GDPR by design, and every claim tested
An address is personal data. This page lists exactly what Addrova logs, stores and never keeps. Each statement has an identifier and is tied to an automated test that runs on every change of the code.
The zero-log claims
In these tables, "address" means any customer input: free text, components, requested coordinates, batch files. Source of truth: the zero-log specification of the API, in French; this page translates it without changing its meaning.
Specification version of 2026-10-19
What is logged (exhaustive list)
| Id | Statement | Automated test |
|---|---|---|
| ZL-01 | One line per HTTP request, with exactly: method, route template (/v1/geocode, never the real URL or the query string), status, duration, pseudonymised tenant, random request id. No IP address, no user-agent, no API key. | api/tests/test_zerolog.py::test_access_log_exact_fields |
| ZL-02 | The only other messages emitted by Addrova's code are those in the inventory below; their parameters are job identifiers, counters, statuses or exception type names. | api/tests/test_privacy_claims.py::test_log_inventory_matches_code |
| ZL-03 | The tenant is pseudonymised everywhere (logs, traces): 16 hexadecimal characters of a salted SHA-256 of its identifier; the plain identifier never appears. | api/tests/test_observability.py::test_tenant_hash_is_stable_and_opaqueapi/tests/test_zerolog.py::test_access_log_exact_fields |
What is never logged nor traced
| Id | Statement | Automated test |
|---|---|---|
| ZL-10 | No address, no requested coordinate and no API key appears in the logs, on every route (geocoding, reverse, autocomplete, repair, batch, Google and Nominatim compatibility), including on error (engine down, rejected input, invalid parameter, wrong key, unknown route). | api/tests/test_zerolog.py::test_canary_never_logged |
| ZL-11 | Third-party library messages are neutralised: parameters masked, query string erased, exception reduced to its type (no message, no trace). | api/tests/test_zerolog.py::test_third_party_logger_args_and_query_redacted |
| ZL-12 | Error responses do not return the offending value nor the engine's internal message. | api/tests/test_api.py::test_geocode_invalid_paramsapi/tests/test_api.py::test_engine_errors_do_not_echo_query |
| ZL-13 | OpenTelemetry traces carry only allow-listed attributes (method, route template, status, pseudonymised tenant, engine operation, country, result count, precision of the first result), with no events; no address, no coordinate, no key, no plain tenant identifier. | api/tests/test_observability.py::test_spans_carry_only_allowlisted_attributesapi/tests/test_observability.py::test_span_content |
| ZL-15 | Console: the email address, magic-link tokens and session tokens appear in no log. | api/tests/test_console.py::test_email_and_tokens_never_logged |
| ZL-16 | Public site demo (/demo/v1): neither the entered address nor the visitor's IP address appears in the logs; the IP is never stored, only an HMAC fingerprint with a daily secret (unusable after rotation) is used to count requests. | api/tests/test_demo.py::test_demo_never_logs_address_or_ipapi/tests/test_demo.py::test_fingerprint_rotates_daily_and_hides_ipapi/tests/test_demo.py::test_redis_mode_shares_secret_and_counters |
| ZL-14 | Prometheus metrics only have bounded label values; an unknown path is counted as <unmatched>. | api/tests/test_observability.py::test_metrics_labels_are_boundedapi/tests/test_observability.py::test_spans_carry_only_allowlisted_attributes |
What is stored, and for how long
| Id | Statement | Automated test |
|---|---|---|
| ZL-20 | API keys are stored only as a SHA-256 fingerprint (plus 12 prefix characters for display). | api/tests/test_tenancy_persistent.py::test_keys_are_hashed_and_revocable |
| ZL-21 | The database holds no request and no result: only tenants, keys (fingerprints) and usage counters (tenant, day, endpoint, count). | api/tests/test_privacy_claims.py::test_schema_has_no_content_columns |
| ZL-22 | Batch: the input file is deleted as soon as processing ends; the result and the job metadata are deleted at the end of the retention period (1 hour after completion by default, configurable) or immediately on DELETE; a job never processed is purged after 24 hours. | api/tests/test_batch.py::test_csv_roundtrip_usage_and_input_purgeapi/tests/test_batch.py::test_isolation_delete_and_expiryapi/tests/test_batch.py::test_result_purged_after_retention |
| ZL-23 | Batch job metadata contains no row of the file (counters, dates, column names, default country). | api/tests/test_privacy_claims.py::test_batch_metadata_has_no_row_content |
| ZL-25 | Console: the only personal data stored is the account's email address (and that of a pending sign-in link, valid 15 minutes, single use); link and session tokens are stored only as a SHA-256 fingerprint. | api/tests/test_privacy_claims.py::test_schema_has_no_content_columnsapi/tests/test_console.py::test_link_is_single_use_and_logout |
| ZL-26 | An organization sees neither the keys nor the usage of another; a revoked or expired (rotated) key is refused. | api/tests/test_console.py::test_org_isolationapi/tests/test_console.py::test_keys_lifecycle_and_plan_limit |
| ZL-27 | Batch-completion webhook: the payload contains only the job's identifier, status, counters and links, never a row of the file nor an address; the webhook's URL and secret are never logged. | api/tests/test_batch_stream.py::test_webhook_delivery_signed_without_addresses_and_retried |
| ZL-28 | Idempotency (Idempotency-Key): only a SHA-256 fingerprint of the request (and, for a batch, the job identifier) is kept for 24 hours, never the address sent nor the response. | api/tests/test_idempotency_versioning.py::test_idempotency_never_stores_addressapi/tests/test_idempotency_versioning.py::test_in_progress_conflict_and_stores_keep_no_content |
| ZL-29 | No undeclared network egress: deny by default in the namespace; the website has no egress; the API only reaches the cluster DNS, its databases (PostgreSQL, Redis), the internal OTel collector and, for webhooks, HTTPS to the public Internet (private, local and metadata ranges excluded). No third-party analytics tool can receive a request. | api/tests/test_k8s_hardening.py::test_egress_is_limited_to_declared_flowsapi/tests/test_k8s_hardening.py::test_namespace_is_restricted_and_denies_by_default |
| ZL-24 | One customer's batch job is invisible to other customers. | api/tests/test_batch.py::test_isolation_delete_and_expiry |
What does not exist (yet)
| Id | Statement | Automated test |
|---|---|---|
| ZL-30 | The asynchronous fallback of /v1/repair (LLM) is disabled: no external call, no address leaves the service. | api/tests/test_repair.py::test_repair_unrepairable_and_async_disabled |
| ZL-32 | No email is actually sent in v0 (in-memory outbox) and no real billing provider is connected (dummy provider). | api/tests/test_privacy_claims.py::test_no_external_transport_by_default |
| ZL-31 | No result cache is in place in v0 (the rotating-secret HMAC cache of ADR 0005 is not implemented yet). | api/tests/test_privacy_claims.py::test_no_result_cache_in_v0 |
Log message inventory
These are the only messages the code emits, in their exact format. A test fails if the code and this list diverge.
%s %s %d %.1fms tenant=%s rid=%s
job %s queued rows=%d
job %s %s rows=%d matched=%d
job %s failed: %s
engine loaded: %s
addrova_engine not installed, using the fake engine
usage flush failed: %s
index preloaded: %d bytes
webhook job %s attempt %d status %s
engine contract violation: candidate without source
engine failure: %sOutside the scope of these tests
These points are checked another way, and we say so:
- Ingress / proxy: its access log must be disabled on the API routes (ADR 0004); infrastructure configuration, to be checked in infra/ and on the cluster.
- Engine (addrova_engine): it commits not to log requests (ADR 0002); its own tests check it.
- Trace export: only if ADDROVA_OTLP_ENDPOINT is set, to a collector hosted in the EU; without this setting, no trace leaves the process.
- Batch file storage: v0 on the container's local disk (permissions 0600); encrypted EU object storage planned for production (ADR 0006, ADR 0009).
Because nothing is kept, nothing is reused
Addrova does not use your requests to train models, build datasets or enrich its index, and the code makes that concrete: queries and results are never logged (ZL-10), the database holds no request nor result (ZL-21), batch files are deleted after processing (ZL-22), there is no result cache (ZL-31) and no address is sent to an external service (ZL-30).
Not claimed yet
- EU-only hosting: the hosting provider and region are not published yet. [À VÉRIFIER]
- Sub-processors and Data Processing Agreement (DPA): to be published, with online signature, before launch. [À VÉRIFIER]
- Certifications: none claimed. ISO 27001 is planned for a later stage.
Data sources and licences
We rely on official open data under open licences (for France, Licence Ouverte 2.0). Every API response carries an attribution array naming its sources. Data under share-alike licences, such as OpenStreetMap, lives in a separate index and is never merged with the official one.
Compliance questions
Can I verify these claims?
Each claim names the automated test that checks it. The tests run on every change; a test fails if one of them disappears or if the log inventory no longer matches the code. Access to the test suite for audits is to be defined. [À VÉRIFIER]
Do you log my IP address?
The application does not: its access log has no IP address, no user-agent and no API key (ZL-01). The ingress in front of it must also have its access log disabled; that is an infrastructure setting, listed above as outside the scope of the tests.
How long are batch results kept?
One hour after completion by default, or until you delete the job (ZL-22). The input file is deleted as soon as processing ends.