import { selectKeyStore } from "../lib/key-store.js"; import { PropertyDataError } from "../lib/propertydata-api.js"; const BASE_URL = "https://api.propertydata.co.uk"; const REQUEST_TIMEOUT_MS = 15_000; // Validate the key against PropertyData's /valuation-sale endpoint with no // inputs — a valid key returns HTTP 400 + body code 701 "Missing input: // property_type" (proof the key was accepted), without consuming a credit. // An invalid key returns auth failure (401/403) or a different upstream code. // Evidence: probing `/national-hpi` returns code X01 "Invalid API endpoint" // — that path does not exist on PropertyData; `/valuation-sale` does. async function validateApiKey(apiKey: string): Promise { // Probe /valuation-sale with one input. Evidence-backed envelopes: // invalid key → {code:"X03", message:"Invalid input: key"} // valid key → {code:"701", message:"Missing input: property_type"} (no credit) // PropertyData does not charge for missing-input errors, so this is a free probe. const url = `${BASE_URL}/valuation-sale?key=${encodeURIComponent(apiKey)}&postcode=SW1A1AA`; const response = await fetch(url, { headers: { Accept: "application/json" }, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); const body = await response.text().catch(() => ""); if (response.status === 401 || response.status === 403) { throw new Error(`PropertyData rejected the API key (HTTP ${response.status}).`); } let parsed: { status?: string; code?: string; message?: string } = {}; try { parsed = JSON.parse(body); } catch { /* fall through */ } if (parsed.code === "X03") { throw new Error(`PropertyData rejected the API key (code=X03 ${parsed.message ?? "Invalid input: key"}).`); } // 701 (missing-input) or status:"success" both prove the key was authenticated. // Any other envelope: surface raw so the operator sees the upstream error. if (parsed.status === "success" || parsed.code === "701") return; if (parsed.status === "error") { throw new Error( `PropertyData validate probe returned unexpected envelope (code=${parsed.code ?? "?"} message=${parsed.message ?? "?"}).`, ); } if (!response.ok) { throw new PropertyDataError(response.status, "valuation-sale", body.slice(0, 200)); } } export async function keyRegister(params: { apiKey: string; accountId: string; }): Promise { const { apiKey, accountId } = params; await validateApiKey(apiKey); await selectKeyStore().save(accountId, apiKey); console.error(`[property-data] key-op op=register account=${accountId}`); }