What you will build
A server-side command-line program that searches up to three pages, lets you choose two product identities and prints their observed delivered prices. Choose either TypeScript or Python; both follow the same workflow. It reads data only and never creates watches or purchases.
Prepare the key and runtime
Use Node.js 22.13+ with npm, or Python 3.11+ with uv. In an empty project directory, install the matching SDK below. Create a test-environment key with catalog:read and compare:read and the matching subscription entitlements. Set HINTADATA_API_KEY privately in the process environment; never put it in the source, browser code or version control. Start with representative test data. A live key requires current source evidence and may consume usage credits.
TypeScript
npm init -y
npm install https://hintadata.fi/sdk/price-compare-typescript-3.1.0.tgz
npm install --save-dev typescript tsx @types/nodeSave the complete program as compare.ts.
import { Configuration, CatalogApi, ComparisonsApi, ResponseError } from '@pricecompare/sdk';
async function main() {
const key = process.env.HINTADATA_API_KEY;
if (!key) throw new Error('Set HINTADATA_API_KEY privately.');
const config = new Configuration({
basePath: 'https://hintadata.fi/v1', accessToken: key,
fetchApi: (url, init) => fetch(url, { ...init, signal: AbortSignal.timeout(20000) }),
});
const catalog = new CatalogApi(config);
const comparisons = new ComparisonsApi(config);
const query = process.argv[2] || 'sony';
const selected = process.argv.slice(3);
const candidates = new Map<string, string>();
let offset = 0;
// Bound time and usage; follow the server cursor, not a guessed page number.
for (let page = 0; page < 3; page++) {
const result = await catalog.searchProducts({ q: query, limit: 2, offset });
for (const item of result.items) candidates.set(item.canonical_product_id, item.product_name);
if (result.next_offset == null) break;
if (!Number.isInteger(result.next_offset) || result.next_offset <= offset) throw new Error('Invalid pagination cursor.');
offset = result.next_offset;
}
if (!candidates.size) { console.log('No matches. Refine your query.'); return; }
if (!selected.length) {
console.table([...candidates].map(([id, name]) => ({ id, name })));
console.log('Run again with two distinct IDs from the same comparable category. At most three pages are read.');
return;
}
if (selected.length !== 2 || new Set(selected).size !== 2 || selected.some(id => !candidates.has(id))) {
throw new Error('Select two distinct IDs from these search results. Refine the query if necessary.');
}
const result = await comparisons.compareProducts({ ids: selected.join(',') });
for (const product of result.products) {
const offer = product.best_offer;
console.log(JSON.stringify({ id: product.canonical_product_id, name: product.comparison_name,
delivered_price: offer?.delivered_price ?? null }));
}
console.log('Observed offers, not a purchase guarantee. Review availability, observation time and seller terms before deciding.');
}
main().catch(error => {
console.error(error instanceof ResponseError ? 'API request failed: HTTP ' + error.response.status + '. See Errors and limits.' : 'Integration stopped. Check configuration, query and selected IDs.');
process.exitCode = 1; // Never print credentials, raw response bodies or an SDK exception.
});npx tsx compare.ts sony
# Replace these with two IDs printed by the first command.
npx tsx compare.ts sony CANONICAL_ID_1 CANONICAL_ID_2Python
uv venv
uv pip install https://hintadata.fi/sdk/price_compare-3.1.0.tar.gzSave the complete program as compare.py.
import asyncio
import os
import sys
import price_compare
from price_compare.api.catalog_api import CatalogApi
from price_compare.api.comparisons_api import ComparisonsApi
from price_compare.exceptions import ApiException
async def main():
key = os.environ.get("HINTADATA_API_KEY")
if not key:
raise ValueError("Set HINTADATA_API_KEY privately")
config = price_compare.Configuration(host="https://hintadata.fi/v1")
config.access_token = key
query = sys.argv[1] if len(sys.argv) > 1 else "sony"
selected = sys.argv[2:]
candidates = {}
offset = 0
async with price_compare.ApiClient(config) as client:
catalog = CatalogApi(client)
comparisons = ComparisonsApi(client)
for _ in range(3):
result = await catalog.search_products(q=query, limit=2, offset=offset, _request_timeout=20.0)
for item in result.items:
candidates[item.canonical_product_id] = item.product_name
if result.next_offset is None:
break
if result.next_offset <= offset:
raise ValueError("Invalid pagination cursor")
offset = result.next_offset
if not candidates:
print("No matches. Refine your query.")
return
if not selected:
for identity, name in candidates.items():
print(identity, name)
print("Run again with two distinct IDs from the same comparable category. At most three pages are read.")
return
if len(selected) != 2 or len(set(selected)) != 2 or any(identity not in candidates for identity in selected):
raise ValueError("Select two distinct IDs from these search results")
result = await comparisons.compare_products(ids=",".join(selected), _request_timeout=20.0)
for product in result.products:
offer = product.best_offer
print(product.canonical_product_id, product.comparison_name,
offer.delivered_price if offer else None)
print("Observed offers, not a purchase guarantee. Review availability, observation time and seller terms before deciding.")
if __name__ == "__main__":
try:
asyncio.run(main())
except ApiException as error:
print("API request failed: HTTP", error.status, "See Errors and limits.", file=sys.stderr)
sys.exit(1)
except Exception:
print("Integration stopped. Check configuration, query and selected IDs.", file=sys.stderr)
sys.exit(1)
uv run --no-project python compare.py sony
# Replace these with two IDs printed by the first command.
uv run --no-project python compare.py sony CANONICAL_ID_1 CANONICAL_ID_2Choose products deliberately
The first invocation prints canonical IDs and names, not a recommendation. Choose two distinct products from the same comparable category and check model and variant in the API reference. The second invocation validates your selection against the bounded search before calling compareProducts. It never silently chooses the first two hits. If the desired ID is outside the first three pages, narrow the query; next_offset is the server continuation, and null means the end.
Recognize a successful result
The second invocation prints one row per compared product: canonical ID, comparison name and best_offer.delivered_price. Missing values stay missing, never zero. These are observed offers, not a forecast, reservation or purchase guarantee. Before using a price, inspect the offer availability, observation time and seller terms in the full typed response. Do not infer currency or tax treatment from an unlabelled number.
Handle empty results and errors
No matches is a normal outcome: refine the query. Duplicate, unknown or excessive selected IDs stop before comparison. Requests have a 20-second timeout and no automatic retry. An HTTP error exits nonzero and prints only its status, never a raw SDK exception. For 401 check the key, for 403 check scopes and entitlement, and for 503 source_unavailable/source_stale contact the source owner. Do not replace unavailable live evidence with demo data. For transient 429/5xx, use the bounded retry guidance before restarting; do not build an unbounded loop.