Documentation  ›  API Reference
Explore the documentation — select a guide below:

Choose your intake

Both accept the same EVIDE JSON payload and finalize the intake at once. The ESB endpoint also accepts two optional ESB fields, which the Standard endpoint refuses. The difference is what happens after intake.

Standard Intake
POST /api/intake/json

Anchors the submitted object immediately. The evidentiary boundary is crossed at the moment of submission. Use this when the source system has already reached a stable, closed decision.

ESB Buffer
POST /api/intake/esb

Anchors the submitted object immediately, like the Standard intake, and opens a declared observation window on it. Use this when the source system needs to declare, over time, how a condition evolves before declaring a final stabilization verdict. Details below ↓

payload
↓
POST /intake/json
↓
Anchored
payload
↓
POST /intake/esb
↓
Anchored + window open (EAR: OPEN · PENDING)
↓
POST /buffer/update (any number of times)
↓
POST /buffer/close
↓
Declared verdict recorded (next EAR version)

ESB Buffer (Epistemic Stabilization Buffer) Operational Intake Profile

An observation window on an intake that is already final

ESB is an optional operational profile for EVIDE intakes. The intake is accepted, hashed and finalized exactly as with the Standard intake, and its first EAR version is generated at once. ESB then opens a declared observation window on that record, during which the source system can record declared observations and, at the end, a declared stabilization verdict. The window never delays or conditions the intake.

ESB does not modify the EVIDE JSON Schema and does not introduce a new schema version.
It changes how a payload is processed over time after intake — not what the payload means.

When to use ESB instead of standard intake

Standard intake (/api/intake/json) anchors the submitted object immediately: the evidentiary boundary is crossed at the moment of submission. ESB (/api/intake/esb) anchors it in the same way, at the same moment, and is for cases where the source system also needs to declare, over a declared period, how a condition evolves and then declare a stabilization verdict. The same payload schema applies to both. What stays open with ESB is the observation window, not the intake: the intake and its first EAR version are final from submission, and the closure adds a new EAR version without changing them.

Endpoints

Endpoint Purpose
POST /api/intake/esb Finalizes the intake exactly as the Standard intake does, then opens the buffer. Every successful intake on this endpoint opens a buffer, whether or not the two optional ESB fields (below) are declared. Response: the Standard intake response plus profile: "esb", esb_status: "active" and buffer.buffer_id. One buffer per intake: it cannot be added later or reopened.
POST /api/buffer/update Records a declared observation on an open buffer — any number of times before closure, with at least one observation field per call. Does not close the buffer. Response: buffer_id and updated_fields.
POST /api/buffer/close Closes the buffer with a declared final verdict and automatically generates the next EAR version of the intake (trigger_reason: esb_closed). A closed buffer cannot be updated or closed again.

Intake-time fields

Field Description Status
buffer_window_hours Declared observation window, in hours, as a positive integer. It is recorded, not enforced: the buffer is not closed automatically when it expires and stays open until buffer/close is called. The dashboard shows the window utilization (actual duration ÷ declared window), which can exceed 100%. optional
buffer.stabilization_source Declared origin of the stabilization assessment for this buffer. Same vocabulary as stabilization_source in buffer/update below, which can change it while the window is open; buffer/close does not accept it. It is the only member of the buffer object that the platform reads. optional
Both fields are part of the payload and are included in intake_hash; the Standard endpoint refuses them with 422 invalid_field. They are validated when the buffer is opened, after the intake has been recorded: an invalid value (for example buffer_window_hours: "2") leaves the intake recorded, with that value in its hash, and returns 500 esb_initialization_failed with request_recorded: true and the evide_id. The buffer cannot be opened afterwards. Validate both values before sending.

buffer/update and buffer/close fields

Both endpoints require buffer_id. Each other field belongs to one phase, stated in its description: a field sent to the other endpoint is rejected with 422 wrong_phase_field, and a field unknown to the system with 422 unknown_field. Nothing is silently ignored. All values are declarations of the source system: EVIDE records them, it does not verify them.

Field Description Status
buffer_verdict Declared final stabilization outcome. Accepted only by buffer/close — a buffer is created with an implicit pending verdict and cannot declare it explicitly. EVIDE records the declared verdict; it does not establish whether the observed system was in fact stable.

pending — implicit default while the buffer is open. Never sent by the client. stable — declared: the observed state satisfied the declared stabilization conditions for evidentiary boundary crossing. Crossing-sufficient, not absolute epistemic truth. unstable — declared: the observed state did not stabilize. Requires instability_reason and buffer_notes. deferred — declared: no verdict could be crystallized; the state is preserved as unresolved. Requires buffer_notes.
required at close
continuity_state Declared continuity of the observed condition. Accepted only by buffer/update.

coherent · partially_coherent · fragmented · unverifiable
optional
stability_trend Declared direction of stability during the observation window. Accepted only by buffer/update.

improving · degrading · oscillating · static
optional
causal_persistence_signal Declared whether the observed condition retained causal continuity from the prior state. Accepted by both: at closure, a declared value replaces the last observed one; if absent, the last observed value is kept.

present · attenuated · absent · inconclusive
optional
stabilization_score Declared numerical indicator between 0 and 100 supporting the stabilization assessment. Accepted only by buffer/close. Rejected outright if outside this range — never silently clamped. optional
instability_reason Declared cause. Accepted only by buffer/close; required when buffer_verdict = unstable, together with buffer_notes.

authority_conflict · evidence_gap · runtime_drift · observability_loss · contradictory_signals · threshold_fragmentation · unresolved_intervention · unknown
conditional
closure_trigger Declared mechanism that caused the buffer to close. Accepted only by buffer/close.

manual_close · auto_threshold · timeout · downstream_dependency · escalation · evidentiary_freeze
optional
stabilization_source Declared origin of the stabilization assessment. Accepted only by buffer/update — not by buffer/close. It replaces the value declared at intake; the previous value remains in the buffer's event log.

human_review · automated_decay · quorum_resolution · timeout_expiration · external_override · mixed
optional
buffer_notes Free-text notes. Accepted by both. Required at closure when buffer_verdict is unstable or deferred. conditional
signal_count_total Declared total number of signals considered. Accepted by both; at closure, if absent, the last declared value is kept, not reset. optional
unresolved_at_close Declared number of elements still unresolved at closure. Accepted only by buffer/close. optional
test_mode Boolean. Accepted only by buffer/close: true bypasses the two-second minimum window (test environments only). optional
A buffer must observe a temporal window: buffer/close is rejected with 422 temporal_violation if less than two seconds have passed since the buffer was opened at intake, unless test_mode: true is set. Once closed, a buffer cannot be updated or closed again — buffer/update returns 409 buffer_closed and buffer/close returns 409 already_closed, never a silent success. If two closures arrive together, exactly one is applied; the other receives already_closed with the verdict actually recorded.

Timeline — a complete lifecycle

// 1. Open — POST /api/intake/esb
{
  "evide_schema": "2.1",
  // ... same payload fields as standard intake ...
  "buffer_window_hours": 2,
  "buffer": { "stabilization_source": "human_review" }
}
// → { "success": true, "profile": "esb", "esb_status": "active", "evide_id": "...", "intake_hash": "...",
//     ...Standard intake response fields..., "buffer": { "ok": true, "buffer_id": 1 } }
// The intake is already finalized: its first EAR version shows the window OPEN / PENDING.

// 2. Observe — POST /api/buffer/update (any number of times)
{
  "buffer_id": 1,
  "stability_trend": "improving",
  "continuity_state": "coherent",
  "causal_persistence_signal": "present"
}
// → { "success": true, "buffer_id": 1, "updated_fields": 3 }

// 3. Close — POST /api/buffer/close
{
  "buffer_id": 1,
  "buffer_verdict": "stable",
  "closure_trigger": "manual_close",
  "stabilization_score": 88.5
}
// → { "success": true, "buffer_id": 1, "buffer_verdict": "stable", "buffer_open_at": "...", "buffer_close_at": "...",
//     "window_seconds": 196, "closure_trigger": "manual_close", "stabilization_score": 88.5,
//     "causal_persistence_signal": "present",
//     "semantic_note": "crossing-sufficient, NOT absolute epistemic truth" }
// semantic_note is returned only with "stable"; it is null for "unstable" and "deferred".
// The closure generates the next EAR version of the intake (trigger_reason: esb_closed);
// earlier EAR versions are not modified.

Authorization and errors

buffer/update and buffer/close use the same API key checks as intake (active key, activated plan, active account). The buffer must belong to an intake deposited by the user bound to the key. For an intake deposited for an organization, that user must also be a current member of the organization at the time of the call, whatever key is used. Error responses have the shape {"success": false, "error": "...", "message": "..."}.

Error Endpoint · cause HTTP
buffer_not_foundupdate, close · the buffer does not exist or belongs to another user — the two cases are deliberately indistinguishable404
organization_authority_requiredupdate, close · the user is no longer a member of the intake's organization; nothing is written403
missing_fieldupdate, close · buffer_id missing422
no_fieldsupdate · only buffer_id sent422
invalid_valueupdate · value outside the vocabulary422
wrong_phase_field · unknown_fieldupdate, close · field of the other phase · field not recognized422
buffer_closed · already_closedupdate · close — buffer already closed409
invalid_verdict · temporal_violationclose · verdict not stable/unstable/deferred · less than two seconds after opening422
instability_reason_required · invalid_instability_reason · notes_requiredclose · conditions of the unstable and deferred verdicts422
invalid_closure_trigger · invalid_stabilization_score · invalid_causal_persistence_signalclose · value outside the vocabulary or range422
esb_initialization_failedintake/esb · the intake was recorded but the buffer could not be opened500

Requests sent with an Idempotency-Key header can also receive the responses described in Idempotent requests below.

Architectural note. ESB does not make EVIDE a monitoring system: it does not poll, does not watch runtime state on its own, does not enforce the declared window and does not decide when a buffer should close. Every observation and every closure is an explicit, upstream-initiated call. EVIDE records what it is told, when it is told.

A stable verdict is a declaration of the source system: crossing-sufficient — the declared stabilization conditions were declared satisfied — and never presented as absolute epistemic truth. EVIDE records the verdict and its elements; it does not establish whether the observed system was stable. ESB never recomputes the evidentiary profile (FCC, DWC, FAC) computed at intake. Full description: Epistemic Stabilization Buffer.

Idempotent requests (Idempotency-Key) Optional header API v2.2

A network timeout does not tell your system whether EVIDE received the request. Resending it without protection may create a second deposit, a second ESB observation or a second closure attempt. The optional Idempotency-Key header lets you resend the same request safely: within the retention period, EVIDE executes it at most once and returns the same outcome to every retry.

Supported endpoints: POST /api/intake/json, POST /api/intake/esb, POST /api/buffer/update, POST /api/buffer/close. Without the header nothing changes: every request is processed as a new request. The payload schema is unchanged (evide_schema "2.1").

The header

  • Idempotency-Key: <value> — 1 to 128 characters from A–Z a–z 0–9 . _ : -, case-sensitive. Use a UUID v4, generated once for each event you deposit and stored by your system before the first attempt. An empty or malformed value is refused with 400 invalid_idempotency_key and has no effect.
  • Do not put personal data or confidential information in the key: it is kept as a technical record for the retention period.
  • The key is scoped to your API key: the same value used with another API key is an independent key.

What counts as the same request. The endpoint plus the JSON body, compared in canonical form (the same canonicalization used for intake_hash): property order and whitespace do not matter. content_hash is part of the comparison. Any other difference in the body, or a different endpoint, makes it a different request.

Outcomes of a request sent with a key

Situation Response
First requestProcessed normally.
Same key, same request, already completedThe original response, with the same HTTP status and an identical body, plus Idempotency-Replayed: true. Nothing is executed again.
Same key, same request, still being processed409 idempotency_request_in_progress with Retry-After (seconds). Retry later with the same key and body.
Same key, different body or different endpoint409 idempotency_key_reused. Nothing is executed. Use a new key for a new request.
Previous attempt failed without effect (for example a validation error)The same request is processed again. If you correct the payload, it is a new request: use a new key.
Previous attempt interrupted after the deposit was recordedThe retry completes it without creating a second deposit, with Idempotency-Recovered: true. evide_id, intake_hash and intake timestamp are those of the original deposit; if the first EAR version had not yet been generated, it is generated at that moment and carries that time.
Attempt overtaken by a later retry, or by an administrative deletion, while still running409 idempotency_lease_lost. Retry with the same key and body to obtain the current outcome.
Idempotency service temporarily unavailable503 idempotency_service_unavailable. Retry with the same key: the retry returns the outcome without creating a duplicate.
Intake deleted by an administrator410 idempotency_record_deleted, without any data of the deleted record, and no new deposit.

Two identical requests with the same key that arrive at the same time are executed once: the other receives 409 idempotency_request_in_progress or the stored response.

Authentication is always checked first

Every retry goes through the full API key checks (status, client activation, account, organization membership, IP allow-list, rate limit) before any stored response is considered. A revoked or expired key receives 401, never a stored response. Retries count toward the per-minute rate limit. On buffer/update and buffer/close the buffer ownership check also comes first: the buffer of a deleted intake receives 404 buffer_not_found, the same response as a buffer that does not exist or belongs to someone else.

Retention: 7 days. A key is remembered for 7 days from the first request sent with it. After that period it is no longer recognised: a request sent with it is processed as a new request and may create a new deposit. Use a new key for every new event, and do not keep retrying the same key beyond a few hours.

HTTP 410 is final. If the intake created with a key has been deleted by an administrator, retries with that key receive 410 for the rest of the retention period and no deposit is created. Do not retry, and do not resend the request without the key or with a new key, unless you intend to create a new deposit.

ESB and buffer operations

ESB intake whose buffer could not be opened. 500 esb_initialization_failed with "request_recorded": true is a final outcome: the intake exists. A retry with the same key returns the same response and never opens a buffer later.

Buffer operations. With the same key, a repeated buffer/update records the observation once, and a repeated buffer/close returns the original closure response. Without a key, a second buffer/update records a second observation, and a second buffer/close receives 409 already_closed.

Usage and billing

A replayed response does not create a deposit and is not counted toward plan usage: each deposit is counted once. A deposit completed by a recovery retry is counted when the recovery completes, which may fall in the following billing month.

What idempotency does not do

It prevents duplicates caused by retrying the same request, within the limits above. It does not merge deposits sent with different keys, with different bodies, without a key or after the retention period, and it does not establish whether two deposits describe the same event. The key never appears in the intake record, the intake_hash, the EAR or a FEDIS.

Response headers

Header Meaning
Idempotency-Replayed: trueThe response is the stored outcome of an earlier request with this key, including a stored 410.
Idempotency-Recovered: trueThe response completes a request that had been interrupted after its effect was recorded.
Retry-After: <seconds>Sent with 409 idempotency_request_in_progress.

Error codes

Error Message HTTP
invalid_idempotency_keyIdempotency-Key must be 1-128 characters from A-Z a-z 0-9 . _ : - (a UUID is recommended).400
idempotency_key_reusedThis Idempotency-Key was already used with a different request (different body or endpoint). Use a new key for a new request.409
idempotency_request_in_progressA request with this Idempotency-Key is still being processed. Retry later with the same key and body.409
idempotency_lease_lostThis request could not complete because its idempotency state changed. Retry with the same Idempotency-Key and payload to obtain the current outcome.409
idempotency_record_deletedThe record created with this Idempotency-Key no longer exists (deleted by an administrator). The original response is no longer available.410
idempotency_service_unavailableIdempotency service temporarily unavailable. Retry with the same Idempotency-Key: the retry returns the outcome of this request without creating a duplicate.503

All use the usual error shape {"success": false, "error": "...", "message": "..."}.

Example

// 1. First request — POST /api/intake/json
curl -X POST https://app.certifywebcontent.com/api/intake/json \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key_here" \
  -H "Idempotency-Key: 577fdea8-0f64-458e-b96a-f4ca3978fd9b" \
  -d @payload.json
// → HTTP 200 { "success": true, "evide_id": "...", "intake_hash": "...", ... }

// 2. Same request again (for example after a timeout)
// → HTTP 200, header Idempotency-Replayed: true, identical body: same evide_id, same intake_hash

// 3. Same key, different body
// → HTTP 409 { "success": false, "error": "idempotency_key_reused", "message": "..." }

// 4. Same request after the intake was deleted by an administrator
// → HTTP 410, header Idempotency-Replayed: true
//   { "success": false, "error": "idempotency_record_deleted", "message": "..." }

Practical guidance

  1. Generate one UUID v4 per event and store it with the event before the first attempt.
  2. On a timeout, a network error, 409 idempotency_request_in_progress (after Retry-After), 409 idempotency_lease_lost, 429 or 503: retry with the same key and the same body, with increasing waits.
  3. On 409 idempotency_key_reused: stop. Your system sent different content under an existing key.
  4. On 410: stop. The deposit was deleted.
  5. On 400 or 422: correct the request and send it with a new key.
  6. Store evide_id and intake_hash from the first successful response you receive, original or replayed: they are identical.

How to deposit

  1. Log in to the portal with your DAPI code
  2. Create a new request and select the JSON structured type
  3. Paste the JSON payload into the dedicated field
  4. The system validates, canonicalises and computes the SHA-256 hash
  5. Receive the EVIDE code and the downloadable canonicalised file
API Access

API access available for enterprise integrations

An API endpoint is available for depositing EVIDE records directly from external systems. Access is reserved for certified partners and enterprise clients. Contact us to receive technical documentation and access credentials.

Request API access

Ready to deposit?

Log in to the portal with your DAPI code and create your first structured JSON deposit.

Access the portal Try the demo