Docs
On this page
Docs/AVM/Integration Guide

Operate · AVM

Integration Guide

This guide covers production architecture, client behaviour, release handling, monitoring and go-live controls.

text
browser/mobile/user system
          |
          v
customer backend or integration service
          |
          | Authorization: Bearer <Valtaic key>
          v
https://api.valtaic.io
          |
          v
Valtaic Property Valuation API

Never call Valtaic directly from untrusted browser code with a live API key.

Client Responsibilities

A production client should:

  • validate required fields and enums before transmission;
  • create useful optional request_reference values and retain server IDs;
  • supply UPRN wherever possible;
  • store request and response with release_id;
  • use connection pooling and HTTP keep-alive;
  • set bounded connect/read timeouts;
  • interpret row status independently of HTTP status;
  • honour quotas, limits and Retry-After;
  • avoid logging credentials or unnecessary personal address data;
  • route manual referrals and permanent failures to operations.

Python Example

python
import os
import time
import random
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
import requests

BASE_URL = "https://api.valtaic.io"
API_KEY = os.environ["VALTAIC_API_KEY"]

session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
})


def retry_delay(header: str) -> float:
    try:
        return max(0.0, float(header))
    except ValueError:
        try:
            return max(0.0, (
                parsedate_to_datetime(header) - datetime.now(timezone.utc)
            ).total_seconds())
        except (TypeError, ValueError, OverflowError):
            return 0.0


def value_property(payload: dict) -> dict:
    for attempt in range(4):
        response = session.post(
            f"{BASE_URL}/v3/simple-value",
            json=payload,
            timeout=(3.0, 30.0),
        )
        if response.status_code < 400:
            body = response.json()
            if body["status"] != "valued":
                raise RuntimeError(f"non-valued result: {body}")
            return body
        if response.status_code not in {429, 500, 503}:
            response.raise_for_status()
        try:
            error = response.json()
        except ValueError:
            error = {}
        detail = error.get("detail", {}) if isinstance(error, dict) else {}
        code = detail.get("code") if isinstance(detail, dict) else detail
        if code == "monthly_valuation_quota_exceeded":
            response.raise_for_status()
        records = [error, detail]
        if isinstance(error, dict) and isinstance(error.get("errors"), list):
            records.extend(error["errors"])
        if any(isinstance(item, dict) and item.get("retryable") is False
               for item in records):
            response.raise_for_status()
        retry_after = retry_delay(response.headers.get("Retry-After", "0"))
        if attempt == 3 or retry_after > 20:
            # Return control rather than shortening a server-directed wait.
            response.raise_for_status()
        delay = max(retry_after, random.uniform(0, min(20, 0.5 * 2**attempt)))
        time.sleep(delay)
    raise RuntimeError("Valtaic request failed after bounded retries")

This example retries selected HTTP errors, not connection timeouts, permanent quota failures or errors explicitly marked non-retryable. Reconcile uncertain synchronous outcomes before repeating them; the same request reference does not prevent another valuation or charge.

TypeScript Server Example

typescript
const baseUrl = "https://api.valtaic.io";
const apiKey = process.env.VALTAIC_API_KEY;

if (!apiKey) throw new Error("VALTAIC_API_KEY is missing");

export async function valueProperty(payload: unknown) {
  const response = await fetch(`${baseUrl}/v3/simple-value`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
    signal: AbortSignal.timeout(30_000),
  });

  const body = await response.json();
  if (!response.ok) {
    throw new Error(`Valtaic ${response.status}: ${JSON.stringify(body)}`);
  }
  if (body.status !== "valued") {
    return { kind: body.status, result: body };
  }
  return { kind: "valued", result: body };
}

This minimal example omits retries. Implement them only for the transient statuses documented in Errors and retries.

Request References and Server IDs

request_reference is an optional customer-defined correlation value. It is neither the authoritative valuation identifier nor an idempotency key for synchronous requests. execution_id identifies the API execution and valuation_id identifies each property valuation; retain both server-generated identifiers.

Recommended pattern:

text
<system>-<entity>-<valuation-date>-<attempt-version>

Do not place secrets or unnecessary personal data in it.

Release Discovery

At application startup and periodically thereafter, call GET /v3/releases/current. Use it to discover:

  • active release ID;
  • model effective date;
  • allowed valuation-date window;
  • supported countries/property types;
  • synchronous and asynchronous limits;
  • published endpoints.

Cache for minutes, not indefinitely. Never hard-code a rolling date boundary.

Valuation Release Changes

When the active release changes:

  1. record the new release ID and effective date;
  2. keep old valuations tied to their original release;
  3. do not rewrite history silently;
  4. rerun a customer acceptance sample if the integration is material;
  5. distinguish expected valuation movement from API contract errors;
  6. use V3 contract tests to detect incompatible response changes.

The same input can have a different point estimate under a new release. That is expected. Separately, use the current API schema and maintain contract tests; a valuation release ID is not an identifier for every API software deployment. Retain the original response instead of relying on a later replay to reproduce it. See Versioning and Releases.

Batch Design

For synchronous batches:

  • stay below the lower of release and plan row limits;
  • account for JSON body size, not only row count;
  • use a useful request_reference per row where your own reconciliation needs one;
  • inspect every row status;
  • use simple batch for high-throughput value refreshes;
  • avoid requesting expensive evidence unless needed.

For larger work, use jobs and idempotency when the job endpoint appears in the active release metadata. Do not emulate a job by holding a single synchronous connection open indefinitely. See Limits and Safeguards and Latency and Throughput when setting batch sizes, timeouts and concurrency.

Address Resolution

Every existing-property request supplies building/name, street and postcode. A supplied UPRN is verified against that address; an omitted UPRN is resolved. If you depend on address resolution:

  • retain the submitted address for reconciliation;
  • treat fuzzy/non-exact errors as data-quality work, not transient outages;
  • do not change an address automatically until it happens to match;
  • allow users or operations to select the correct registered unit;
  • cache resolved UPRNs in your own authoritative property record where lawful.

Stored Data in Customer Interfaces

If your product lets users choose whether Valtaic can fill blank attributes:

  • default to use_stored_data=false; enable it deliberately only when your key has the required scope;
  • clearly state that explicit values override stored values;
  • show fields returned in stored_data_used;
  • show ignored inputs when they explain why a field had no effect;
  • do not imply that stored data is guaranteed complete.

Security and Privacy

  • Use TLS only.
  • Keep API keys in managed secrets.
  • Apply least-privilege scopes per integration.
  • Rotate keys without downtime.
  • Avoid storing more address data than the use case requires.
  • Encrypt requests/results at rest in your systems.
  • Control who can view comparables and property history.
  • Log IDs and outcome codes rather than full payloads where possible.
  • Define retention for valuation requests and results.

Observability

Capture:

text
endpoint
request_reference
execution_id
valuation_id
X-Valtaic-Request-Id
release_id
HTTP status
row status
latency
retry count
batch summary
job_id

Monitor:

  • latency percentiles by endpoint and batch size;
  • 429, 500, 503 rates;
  • identity failure rate;
  • manual referral rate;
  • valued-row rate;
  • quota consumption;
  • asynchronous completion time and failure rate;
  • release changes.

Use the service-level terms in your agreement when setting latency and throughput expectations.

Failure Routing

ResultProduct action
valuedDisplay and store the value, range, confidence and release ID.
referredOffer manual review workflow.
rejectedCorrect the structural or identity error in errors[].
valuation_failed, retryableRetry under policy, then escalate.
valuation_failed, permanentCorrect data or escalate.

Go-Live Checklist

  • Production hostname and TLS certificate are confirmed.
  • Required endpoints appear in /v3/releases/current.
  • Live key is stored server-side and has least-privilege scopes.
  • Plan row, monthly unit, rate and concurrency limits are understood.
  • Valuation dates use the discovered rolling window.
  • Contract-invalid requests are rejected in test.
  • Supplied-UPRN verification and UPRN-resolution paths are both tested.
  • Stored-data true/false and explicit precedence are tested.
  • Freehold/leasehold and simple/detailed lease paths are tested.
  • Flat, house, bungalow and development conditions are tested.
  • Batch partial outcomes are reconciled correctly.
  • Job idempotency and polling are tested if used.
  • Manual referrals enter a human workflow.
  • Retry logic excludes permanent errors.
  • Request references, execution IDs, valuation IDs, release IDs and edge request IDs are logged.
  • Keys and address payloads are redacted appropriately.
  • Customer support has the error and escalation fields it needs.

Contract Testing

Maintain fixtures for:

  • one freehold house;
  • one simple-mode leasehold;
  • one detailed-mode leasehold;
  • one flat with inapplicable fields;
  • one address-resolved property;
  • one stored-data-disabled property;
  • one development property;
  • one partial batch;
  • one asynchronous job;
  • one manual referral.

Contract tests should verify schema, status and invariant behaviour rather than requiring exact valuation equality across monthly releases.