Skip to content
Sandbox

Observation coverage

Declare what you supplied for an account over a period, so that a missing due date can be read as a miss rather than as an unknown.

View as Markdown

An observation coverage window is a declaration: "every operation on this account between these two dates has been supplied to you". It is a fact about an account and a period, not about a transaction. It has its own resource, POST /transactions/v1/coverage-windows, rather than a field on POST /transactions/v1/enrichments, because it exists precisely when there is no transaction to send: a month with no direct debit is the case where the declaration matters most.

Why an absence proves nothing

Transaction Intelligence can tell that an expected due date has not arrived. On its own, it cannot tell whether that is because nothing happened or because nobody sent it. Without that distinction, "the client stopped calling the API" and "the subscription stopped collecting" would be one and the same thing.

Coverage is never inferred from traffic: ten calls in October may be ten operations out of a hundred. It requires an explicit declaration per account and per period. Until one exists, lifecycle.state stays unknown, never late: "I saw nothing" does not become "nothing happened".

Declaring coverage

POST/transactions/v1/coverage-windows

Declare what was supplied for an account, over a period

Reference Transaction Intelligence

Request bodyrequired

application/json · CoverageWindow

  • account_hintobjectrequired

    The account concerned. iban when it has one, otherwise external_account_id, your own identifier, for cards, wallets and non-SEPA rails. At least one of the two. Neither is stored: we keep only an opaque fingerprint, scoped to your workspace, the same one as on the enrichment, without which the coverage would never meet the series it qualifies.

  • ibanstring
    ^[A-Z]{2}[0-9]{2}[A-Z0-9]{10,30}$
  • external_account_idstring · length 1–128
  • covered_fromstring · daterequired
  • covered_tostring · daterequired

    We do not certify what has not happened yet: a future date is rejected.

  • completenessstring

    complete means something precise: you assert that all the operations on this account over this interval have been supplied to us. It is the only value that allows us to conclude from an absence. partial and unknown do not: ten calls in October may be ten operations out of a hundred.

    completepartialunknown

Responses

200Coverage recorded, or merged with a neighbour.CoverageWindowResult
400Unreadable body, or undeclared host.Problem
401Key missing, unknown or revoked.Problem
403Organisation or workspace inactive.Problem
413Body too large.Problem
415The body must be application/json.Problem
422Closed schema: unknown field, account missing or malformed, non-existent day, interval reversed or in the future, unknown completeness. errors[].code names the rule, and the list is the runtime's own, by construction: must_be_object, unknown_field, required, malformed_iban, must_be_non_empty_string, too_long, contains_control_character, account_identity_required, must_be_iso_date, after_covered_to, unknown_completeness, interval_in_future. must_be_iso_date covers form and calendar (2026-02-30 is rejected). interval_in_future: covered_to beyond tomorrow, by the server's date in UTC. provenance is not an input field: the closed schema rejects it as unknown_field, and the store never sees it.Problem
429rate_limited: the operational rate limit has been reached, for this key or for the workspace (the detail says which). Nothing was computed, nothing was stored, no event was emitted. Replay after Retry-After. See x-noryo-rate-limit.Problem
500Internal error.Problem

The body is a CoverageWindow, a closed schema: any unknown field is rejected (422).

Schema CoverageWindow

Closed schema: an unknown field is rejected (422). In particular, `provenance` is not an input field.

  • account_hintobjectrequired

    The account concerned. iban when it has one, otherwise external_account_id, your own identifier, for cards, wallets and non-SEPA rails. At least one of the two. Neither is stored: we keep only an opaque fingerprint, scoped to your workspace, the same one as on the enrichment, without which the coverage would never meet the series it qualifies.

  • ibanstring
    ^[A-Z]{2}[0-9]{2}[A-Z0-9]{10,30}$
  • external_account_idstring · length 1–128
  • covered_fromstring · daterequired
  • covered_tostring · daterequired

    We do not certify what has not happened yet: a future date is rejected.

  • completenessstring

    complete means something precise: you assert that all the operations on this account over this interval have been supplied to us. It is the only value that allows us to conclude from an absence. partial and unknown do not: ten calls in October may be ten operations out of a hundred.

    completepartialunknown

The account is named through account_hint: iban when it has one, otherwise external_account_id, your own identifier for cards, wallets and rails without an IBAN. At least one of the two is required (account_identity_required). Neither is stored: Noryo keeps only an opaque fingerprint, scoped to your workspace, the same one computed on the enrichment. Use the same account_hint in both calls: a transaction sent without it has no source, and no coverage will ever meet it.

covered_from and covered_to are dates in the form 2026-07-01. A non-existent day is rejected (must_be_iso_date), and so is a reversed interval (after_covered_to). Noryo does not certify what has not happened yet: a covered_to beyond tomorrow, by the server's date in UTC, is rejected (interval_in_future).

The exact meaning of completeness

ValueWhat you are assertingAllows a conclusion from an absence
completeEvery operation on this account over this interval has been supplied.Yes, and it is the only one.
partialOnly some of them have been supplied.No.
unknownYou do not know.No.

complete has a precise meaning: all operations on the account over the interval have been transmitted, including when there were none. It is the only value that allows an absence to be read as a miss.

Warning

When completeness is omitted, the contract applies complete. Do not leave it out for convenience: declare complete only over periods where your synchronisation genuinely guarantees it.

Provenance is not an input

The response carries provenance: caller_asserted, always. It is not a field you choose: Noryo records what you assert, does not verify it, and you cannot declare anything else. Sending provenance in the body is rejected as unknown_field.

Response and interval merging

Schema CoverageWindowResult

What the declaration became once recorded. `merged_intervals` counts the pre-existing intervals this declaration absorbed: `0` when it stands alone, `1` on an exact replay (it absorbed its own copy), `1` or more when it touches or overlaps earlier declarations. `covered_from` / `covered_to` are the bounds after merging. Measured at the public edge: a first version of this document said "1 if it stands alone"; the engine was right, the documentation was not.

  • covered_fromstring · daterequired
  • covered_tostring · daterequired
  • completenessstringrequired
    completepartialunknown
  • provenancestringrequired

    Always caller_asserted: we record what you assert, we do not verify it, and you cannot declare anything else.

    caller_asserted
  • merged_intervalsinteger ≥ 0required
  • policy_versionstringrequired
  • notestring
  • request_idstringrequired

The response is a 200 in both cases, recorded on its own or merged with a neighbour. covered_from and covered_to are the bounds after merging. merged_intervals counts the pre-existing intervals the declaration absorbed:

Situationcovered_from / covered_tomerged_intervals
First declaration, 2026-07-01 to 2026-07-312026-07-01 / 2026-07-310
Declaration of 2026-08-01 to 2026-08-15, adjacent to the previous one2026-07-01 / 2026-08-151
Exact replay of the previous declaration2026-07-01 / 2026-08-151

Two intervals for the same account that touch or overlap become one: an hourly synchronisation does not manufacture intervals, and an exact replay returns 1 because the declaration absorbs its own copy. The call is idempotent by construction, with no Idempotency-Key header.

What coverage changes in a series

Coverage acts on the lifecycle axis of a recurring series (see Series lifecycle). For each closed due date, Noryo checks that every source of the series was covered complete over the whole expected window (expected_next_window, from from to to); a coverage that contains only part of the window does not allow a conclusion.

lifecycle.observation_coverageMeaningWhat an absence allows you to conclude
not_requiredNo closed due date: nothing had to be observed.Nothing to conclude; no miss is possible.
completeEvery source was covered over the expected window.A miss is a miss: late, then inactive when consecutive misses repeat.
incompleteAt least one source was not.Nothing: state is unknown, and reason says why.
unknownThe sources are unknown or too numerous to check.Nothing: state is unknown.

consecutive_misses only advances under complete coverage. ended is never produced by an accumulation of absences, with or without coverage: it requires explicit terminal evidence.

What coverage does not change: status (how much of a series this is) and history_coverage.sufficiency (whether the cadence can be trusted). Both readings depend on the occurrences observed, not on your declarations.

What Noryo does not verify

Noryo does not query your bank and does not compare your declarations with what you sent. A complete coverage declared over a period where operations were missing will produce late and inactive states that do not exist: the declaration is the caller's responsibility. The contract exposes no way to read back or withdraw a declaration: declare what you know, at the pace of your synchronisation.

Examples

A SEPA account, with July supplied in full:

curl -X POST https://api.noryo.app/transactions/v1/coverage-windows \
  -H "Authorization: Bearer $NORYO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "account_hint": { "iban": "FR7630006000011234567890189" },
    "covered_from": "2026-07-01",
    "covered_to": "2026-07-31",
    "completeness": "complete"
  }'
Shell
{
  "covered_from": "2026-07-01",
  "covered_to": "2026-07-31",
  "completeness": "complete",
  "provenance": "caller_asserted",
  "merged_intervals": 0,
  "policy_version": "cov_v1",
  "note": "",
  "request_id": "req_01J9…"
}
JSON

A prepaid card, named by your own identifier, partial synchronisation:

const response = await fetch("https://api.noryo.app/transactions/v1/coverage-windows", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.NORYO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    account_hint: { external_account_id: "carte-4970" },
    covered_from: "2026-08-01",
    covered_to: "2026-08-31",
    completeness: "partial",
  }),
});

const coverage = await response.json();
console.log(response.status, coverage.merged_intervals, response.headers.get("noryo-request-id"));
TypeScript

Rejections specific to this route

A 422 returns a Problem of type invalid_input, where errors[].code names the rule: unknown_field (a field outside the schema, provenance included), required (account_hint missing or not an object), account_identity_required, malformed_iban, must_be_iso_date (date missing, malformed or a non-existent day), after_covered_to, interval_in_future, unknown_completeness. The full list, including must_be_object, must_be_non_empty_string, too_long and contains_control_character, is in the operation above and on /errors/invalid_input.

The other responses (400, 401, 403, 413, 415, 429, 500) are shared across the API. This route counts towards the rate limits; a 422 consumes a token, since the key was valid. Full catalogue: Errors.

Published on 2026-09-17 11:19 UTC (version 1).

Was this page helpful?