entity_analysis engine whose
released configuration declares an output contract, so it runs on the platform’s
deterministic contract-mode path. No model is involved: evidence and
assessment are a projection of the upstream flood facts, so the same input and
the same upstream data produce the same evidence and assessment on every
run. Two envelope values are still per-run by design and are not part of that
guarantee: request_id is the run’s own ID, and evidence.flood.vintage moves
with the fetch date. Do not use whole-output equality as a replay check.
This page is the integration contract for that engine. For the generic run
lifecycle see the Integration guide; for the endpoint
schemas see Submit Run and
Get Run Status.
Flow
Submission is asynchronous. A202 means the request envelope was accepted and
a run row exists, not that the payload satisfied the engine’s declared input
contract. See Validation timing below.
Submit a run
cotality_response
shown is a minimal synthetic stand-in. In production, replace it with the
complete vendor response for the subject property, every block exactly as the
vendor returned it. See Request contract.
202:
run_id. It is the identifier used by polling, webhook payloads,
delivery history, and support.
Request contract
The request body carries a singleinput object. This engine declares its input
schema in exclusive mode: any field that is not listed below, and is not
nested inside cotality_response, is rejected.
Required-in-practice versus transport-optional: only
line1, zip, and
cotality_response are enforced. city and state are declared optional so a
partial address still produces a run rather than a hard failure, but omitting
them raises the odds of a below-rooftop geocode and therefore of
evidence.flood.status: "unavailable".
cotality_response is passed through verbatim and its shape is defined by the
vendor, not by Nexio. Any non-empty object is a valid submission, which is why
the runnable example in Submit a run works with a synthetic
stand-in. In production, send the complete vendor response, every block exactly
as returned: the contract requires the full payload to be stored with the run,
and there is no canonical Nexio-side schema for it to conform to beyond
non-emptiness and the platform request bounds.
One transport constraint applies: the payload is decoded and re-encoded as
standard JSON on intake and storage, so it is preserved semantically, not byte
for byte. String and boolean values keep their exact content. Numbers are
parsed as IEEE 754 doubles: integer magnitudes beyond 2^53 can be silently
altered (9007199254740993 stores as 9007199254740992), and lexical forms are
normalized (1.50 becomes 1.5, 1e2 becomes 100). The vendor serves opaque
identifiers (propertyId, clip) as strings, so typical responses are
unaffected. Any value whose exact original text must survive should be
transmitted as a JSON string.
Validation timing
Envelope-level checks run synchronously at submission and reject before a run is created: malformed JSON, unknown top-level request fields, missinginput,
request bounds, authentication, capability, rate limit, and monthly cap.
The declared input contract above is validated asynchronously, after the
202. A submission that clears the envelope but violates the declared schema
creates a run that transitions to terminal status failed, and delivers a
run.failed webhook. Read error on the run for the reason. Typical messages:
failed is not always a request defect, so classify it from error before
deciding what to do:
errornames an input path (required input ...,input ... must be ...,input ... does not match ...,input ... is not part of this engine's request contract): a request defect. Fix the payload. Re-submitting it unchanged fails identically.- Any other
error: not a caller defect. Retryable internal failures are retried by the worker before the run goes terminal, so a terminalfailedhere has already used that budget (attempton the run tells you how many executions it took). Re-submitting the same payload once is safe, and if it fails again, report therun_idandtrace_idtosupport@usenexio.comrather than looping.
Response envelope
The output is delivered in two places, identically:data.run.outputon therun.completedwebhook, when the endpoint’spayload_modeisfulloutputonGET /api/v1/runs/{run_id}
Top level
evidence.flood
Flood statuses
Branch onstatus, never on field presence.
unavailable covers two causes that call for opposite responses:
- Address precision. The geocoder resolved the address below rooftop precision (interpolated, geometric center, approximate, or unknown), so the FEMA call was never made. Rooftop precision is a hard floor for flood-zone determination. Re-submitting the same address will not help.
- Transient upstream degradation. The geocoding or FEMA call failed, timed out, was cut off by a budget or circuit breaker, or returned data the engine refused to serve as complete. A later run of the same address can succeed.
warnings array on GET /api/v1/runs/{run_id}, and on an
unavailable result it carries the machine-readable cause. Poll the run once
after an unavailable webhook and branch on it:
retryable: false(for exampleenrichment_precision_below_floorormalformed_field): re-submitting the same address will never help. Fix the address. Ifcityorstatewas omitted, or line 1 is a PO box, a rural route, an intersection, a lot or parcel description, or new construction the geocoder may not have indexed, that is the cause under your control.retryable: true(for exampleenrichment_degradedorenrichment_degraded_circuit_open): the upstream lookup failed transiently. Retry the same address after a delay; a later run can succeed.
warnings appears only on the run read, never in the webhook payload, and only
on runs where a non-informational warning was recorded. If you consume webhooks
without polling, fall back to this triage order: inspect the address first (the
cause you control), retry a complete deliverable address at most once after a
short delay, and after a second unavailable stop retrying in line. Record the
run as carrying no flood signal, and re-run later or send the run_id to
support@usenexio.com if you need the specific cause.
Whatever the cause, unavailable is a valid terminal result, not an error.
Persist the run and the absence of a flood signal rather than dropping it.
Enrichment failure never fails or degrades the run. A run whose flood lookup
degrades still reaches terminal status completed and still delivers a
run.completed webhook, with evidence.flood.status set to unavailable and
an assessment.narrative that says the read could not be completed. Do not
treat a missing flood fact as a delivery problem.
Terminal statuses
The platform-wide run contract defines three terminal statuses:completed,
degraded, and failed. This engine reaches only two of them.
Keep
degraded in your terminal-status switch anyway: it is part of the
platform contract, it is terminal, and treating any terminal status as
non-terminal turns a poll into an infinite loop.
Reserved peril blocks
evidence.wildfire, evidence.coastal_distance, evidence.earthquake, and
evidence.fire_protection are always present and always null in this version.
They are typed object or null in the engine’s published contract, not
null. Later phases populate them as objects without a breaking schema change
on your side. Bind to the full envelope now: parse the keys, tolerate null,
and do not fail closed on an unexpected object appearing in one of them.
vintage
vintage is the UTC calendar date (YYYY-MM-DD) on which the flood data was
fetched from the upstream FEMA source. A cached result replayed on a later run
keeps its original fetch date, so vintage can be older than the run.
vintage is not the FIRM panel effective date and not the date FEMA
published the map. It says when Nexio read the data, not how old the data is. It
is absent when no upstream fetch time exists for the run.
assessment
Both prose fields are templated projections of the evidence, not model output.
Every narrative carries the informational-not-a-determination framing; do not
strip it when you surface the text.
Limits and payload size
The vendor property payload is usually what drives request size, so size the integration against the request bounds rather than assuming a small body.- The HTTP request envelope limit is fixed at the platform ceiling and a
violation returns
413. - This engine’s released configuration raises every configurable JSON bound
(canonical bytes, string length, array items, object fields, depth) to the
maximum configurable ceiling in the platform table. These bounds are structural
and apply to the whole
inputtree, including everything nested insidecotality_response, even though no field in there is type-checked. - Bound violations are deterministic, are returned before the run is created,
and carry
code: "request_bound_exceeded"withdetails.bound,details.path,details.limit, anddetails.actual.
429 with code: "rate_limited" and a Retry-After
header in seconds. Honor the header; do not retry in a tight loop. Monthly run
caps and escalation are on the same page.
Delivery and reconciliation
Terminal results arrive by webhook.run.completed carries the output; any run
that reached terminal failed, from a declared-input violation or from an
internal error, arrives as run.failed.
Deliveries are at-least-once and unordered, so verify the signature over the
raw bytes and deduplicate on X-Nexio-Delivery before processing. Retry
schedules, dead-letter behavior, and resend are documented in
Webhook verification and delivery.
Webhooks are notifications, not the source of truth. After a missed, duplicated,
delayed, or out-of-order callback, reconcile with
GET /api/v1/runs/{run_id} using the run_id you persisted at submission.
Integration checklist
- Persist
run_idat submission and reconcile by polling on any webhook gap. - Treat
completed,degraded, andfailedas terminal, even though this engine only ever reachescompletedandfailed. - Branch on
evidence.flood.status, never on field presence. - Treat
in_sfha: nullas unknown, never asfalse. - On
unavailable, check the submitted address before retrying: correct a partial or non-deliverable address, retry a complete one at most once, then stop. - Parse all five
evidenceperil keys and toleratenulltoday and an object later. - On terminal
failed, classify fromerror: an input-path message is a request defect to fix, anything else is platform-side. - Surface
assessment.narrativewith its informational-not-a-determination framing intact.