> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.fortresstech.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.fortresstech.io/_mcp/server.

# CORA

CORA reviews the compliance documents of an application once every adult applicant has signed. It nudges each applicant to upload what is missing, checks each uploaded document, and writes the result back to Fortress. Fortress keeps the application moving and tells CORA when to look.

## The bundle model

* **Definitions upfront.** [`GET /compliance-documents/manifest`](/api-reference/compliance-documents/get-compliance-documents-manifest) returns every document type with its definition (fields, mappings, rules) before any upload, independent of any application. Definitions are versioned per document type (`definitionVersion`), so CORA builds one Blueprint per version. The bundle carries each document type's `definitionVersion`, not its content. Fortress tells Fission Labs when a definition changes; there is no event for it.
* **One read of the application's data.** [`GET /applications/{applicationId}/full`](/api-reference/applications/get-application-full) returns the application from Fortress' own database (no search index, no sync delay): application, household, every live applicant with customer, addresses, jobs, spouse, minor record, `incomes[]` and `assets[]` lines, deductions, and (with `sensitive: read`) the encrypted sensitive values; only the values the taxonomy compares documents against. The sections follow the API key's grants. The manifest's mappings name the path in this response that each document value is compared against.
* **One read of the documents.** [`GET /compliance-documents/applications/{applicationId}/bundle`](/api-reference/compliance-documents/get-application-bundle) returns requirements → document types → documents, with each document's status, extracted fields and review result, and each document type's `documentTypeKey` and `definitionVersion` (its label, taxonomy domain, recognition text and definition content only with `include`; the manifest is the dictionary for them). The bundle serves the current definition version for each document type. The version can change during an application when a definition is updated; compare `definitionVersion` to pick the Blueprint; a `definitionVersion` with no Blueprint: re-read the manifest. A document type with no approved definition has `definitionVersion: null`.
* **CORA writes document status.** Every status CORA sets goes through [`POST /documents/{documentId}/results`](/api-reference/documents/post-document-results), with or without extracted fields and review results.
* **Fortress derives requirement status.** Fortress derives the status of each requirement from its documents every time the bundle is read. CORA never writes it.
* **Events tell CORA when to look.** What CORA does depends on the event. See [Events](/ai-team-members/cora/events).
  * [`application.signed`](/webhooks/webhook-events/webhooks/application-signed), `requirement.upload_received`, `requirement.upload_removed`, `compliance_review.closed`: re-read the bundle.
  * `compliance_review.opened`: stop writing until `compliance_review.closed`; meanwhile the cancel route and the Results API return `409 COMPLIANCE_REVIEW_OPEN`.
  * The bundle's `complianceReview` carries the open session, or `null` when none is open (a closed review is gone from the bundle; `compliance_review.closed` reports the close): check it on every read, and use it to recover a missed `compliance_review.opened` or `compliance_review.closed`.
  * `agent.state_changed`: pause when the agent is disabled, resume when it is enabled again. Also re-read [`GET /agents/cora/state`](/api-reference/agents/get-agent-state) and apply the new `cadencePolicy` from the next touchpoint.
  * `message.received`: read the applicant's message.
  * `application.completed`: stop all work on the application.

The [Review Flow](/ai-team-members/cora/review-flow) walks through one application from signature to completion. The [API Reference](/ai-team-members/cora/api) lists every route CORA calls.

## Statuses

**Document status.** Set on each document; who writes each one:

| Status                   | Written by                                                                                | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ------------------------ | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NOT_YET_REVIEWED`       | Fortress                                                                                  | Uploaded, not processed yet.                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `EXTRACTING`             | CORA                                                                                      | Quality checks, then reading fields.                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `EVALUATING_RULES`       | CORA                                                                                      | Required fields, rules and mappings.                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `WAITING_FOR_APPLICANT`  | CORA or compliance                                                                        | Waiting for a clarification from the applicant. The conversation happens in the message thread; CORA moves the document to `WAITING_FOR_COMPLIANCE` when the clarification is ready for compliance. Not used for a failed document.                                                                                                                                                                                                                      |
| `WAITING_FOR_COMPLIANCE` | CORA, or Fortress while a manual compliance review is open (`statusSetBy: fortress_user`) | Handed to compliance for manual work. CORA stops working on it.                                                                                                                                                                                                                                                                                                                                                                                          |
| `VALIDATED_BY_CORA`      | CORA                                                                                      | Every check passed.                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `APPROVED`               | Fortress only                                                                             | Approved by a human.                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `REJECTED`               | CORA or staff                                                                             | CORA (or staff) rejected the document: a check failed that needs a new upload (unreadable, wrong document, a rule failed). It stays in the bundle as history only and never counts toward the requirement; the requirement goes back to `AWAITING_UPLOAD` when nothing else counts or it was the only counting document of a required type. The applicant must upload a new one; the new upload restarts the review. `statusSetBy` says who rejected it. |

**Requirement status.** Fortress derives it from the requirement's non-rejected documents (`REJECTED` documents are history only and never count) and the documents Fortress asks the applicant for. The rules read the statuses the bundle shows, so a stored `REJECTED` document that an open manual compliance review covers shows `WAITING_FOR_COMPLIANCE` and counts until the review closes. The first rule that matches wins, blockers first:

1. `AWAITING_UPLOAD`: no non-rejected document counts, or a document Fortress asks this applicant for, and that is not optional (for example the government ID for an adult's identity), has none.
2. `WAITING_FOR_COMPLIANCE`: a counting document is `WAITING_FOR_COMPLIANCE`, whether CORA set it or an open manual compliance review covers it. While a review is open, the bundle shows the documents it covers as `WAITING_FOR_COMPLIANCE` with `statusSetBy: fortress_user`, an earlier human `APPROVED` or `REJECTED` included; closing the review shows the stored statuses again.
3. `WAITING_FOR_APPLICANT`: a document is `WAITING_FOR_APPLICANT`.
4. `UPLOADED`: a document is `NOT_YET_REVIEWED`.
5. `IN_REVIEW`: a document is `EXTRACTING` or `EVALUATING_RULES`.
6. `VALIDATED`: every non-rejected document is `VALIDATED_BY_CORA` or `APPROVED`, and every required document type (`required: true`) has one.

CORA acts on each document's status; the requirement status is the summary (the most urgent state first). A requirement can be `WAITING_FOR_COMPLIANCE` while one of its documents is `WAITING_FOR_APPLICANT`, so nudge and pick up work per document. Fortress is still aligning the requirement status precedence with Fission Labs (CORA).

**Application status.** There is no separate CORA status field on the application. Fortress can derive an application-level CORA status from the requirement statuses of the application.

## When Fortress refuses a write

Fortress can refuse a CORA write with `409`, first match wins: `DOCUMENT_REMOVED`, `COMPLIANCE_REVIEW_OPEN` (a manual compliance review is open; the cancel route answers it too), `STATUS_SET_BY_USER` (a Fortress user set the document's status), or `DOCUMENT_ALREADY_VALIDATED` (the document is already `VALIDATED_BY_CORA`). Nothing is written. CORA must handle it: stop, re-read the [bundle](/api-reference/compliance-documents/get-application-bundle) and continue from the state Fortress returns. A document a human set (`statusSetBy: fortress_user`) is final for CORA, except the temporary `WAITING_FOR_COMPLIANCE` the bundle shows while a manual compliance review is open (`complianceReview` set): after `compliance_review.closed` the stored status shows again.

## Authentication

Every call sends the organization's API key in the `x-api-key` header. One key maps to one organization, server to server only. `401` without an `x-api-key` (gateway) or when Fortress refuses a key that passed it; `403` when the key is unknown or lacks the permission. See [Authentication](/introduction/authentication).

Events are not authenticated by the API key: each delivery is signed with the `X-Fortress-*` headers. See [Webhooks](/webhooks/webhooks#verifying-signatures).

## Cadence policy

The timing of CORA's nudges comes from `cadencePolicy` on [`GET /agents/cora/state?propertyId=`](/api-reference/agents/get-agent-state), not from fixed values in CORA. It is the property's stored policy, or the default (the baseline seed) when the property never set one or has no CORA configuration. `isFallback` is read-only: `true` for that default, `false` for a stored policy. Staff set it in Fortress; there is no partner write. CORA reads it on startup and again on each `agent.state_changed`, and applies a change from its next touchpoint.

* `touchpoints[]`: `offset` (`days` and/or `hours`, strictly increasing), `channels` (`SMS`, `CALL`, `SITE_TASK`) and an optional `purpose`. At a `SITE_TASK` touchpoint CORA stops outreach and raises no task.
* `quietHours` (optional): `start`, `end` (`HH:mm`) and an IANA `timezone`. A touchpoint inside quiet hours is deferred to `end`.
* `expireAfterLastTouchpoint` (`days` and/or `hours`, total above 0): how long after the last touchpoint, without any applicant interaction, CORA waits before it cancels with `CADENCE_EXPIRED`. Always returned; a stored policy that did not set it reads as 7 days.

The touchpoints are the whole cadence. The scope is fixed. There is one cadence per household. The clock starts at [`application.signed`](/webhooks/webhook-events/webhooks/application-signed). An applicant response or upload ([`requirement.upload_received`](/webhooks/webhook-events/webhooks/requirement-upload-received)) does not stop the cadence; it restarts the clock at offset 0. Each touchpoint sends one message per household that covers all outstanding requirements.

The rest of CORA's outreach behaviour is fixed, not part of the policy:

* CORA stops the cadence when the application is complete (every requirement `VALIDATED`) or canceled, denied or abandoned.
* When a channel fails (for example an invalid phone number), CORA may move on to the next channel or touchpoint.
* CORA runs the cadence whenever the agent is enabled (`isEnabled`). The policy has no on/off of its own.

## Conventions

* All routes are under `https://api.fortresstech.io/v1`.
* Field names are camelCase. Ids of Fortress resources (organization, property, household, application, applicant, requirement, document type (`documentTypeId`), document, staff user (`actorId`), session) are UUIDs. `runId`, `checkId`, `ruleId`, `mappingId` and `applicationVersion` are plain strings.
* **Omitted, `null` and `[]` mean different things.** A property left out with the bundle's `exclude` parameter is omitted, not returned empty. `null` means there is no value, for example `definitionVersion: null` for a document type with no approved definition. `[]` is an empty list, for example `reasonCodes: []` when every check passes, or `documents: []` for a requirement with no upload yet.
* Every event shares the same envelope (`eventId`, `eventType`, `occurredAt`, `data`). See [The event envelope](/webhooks/webhooks#the-event-envelope).

More in [Conventions](/introduction/conventions).

## Still aligning with Fission Labs (CORA)

* The cadence policy: its shape, the seed values, and re-reading it on `agent.state_changed` (contract v0.20 names `agent.property_state_changed`).
* Where the policy is served: on `GET /agents/cora/state`, not on the dedicated `GET /properties/{propertyId}/agents/cora/cadencePolicy` of contract v0.20.
* A property with no stored policy: Fortress serves the default with `isFallback: true`, where contract v0.20 answers `null` and back-fills the seed on existing rows.
* `isFallback` and `expireAfterLastTouchpoint`, which contract v0.20 does not define.
* The cadence clock starts at `application.signed` (contract v0.20 names `application.activated`, an event Fortress does not send).
* When outreach stops is fixed, not a policy field (contract v0.20 has a configurable `stopOn`).
* `consolidateRequests`, `invalidChannelAcceleration` and `isActive` from contract v0.20 are fixed behaviour, not policy fields (`isActive` is the agent's `isEnabled`).
* No `deadlineCompression`: the touchpoints define the whole cadence and no deadline is exposed to CORA, so contract v0.20's `deadlineCompression` is dropped.
* No daily possible-cancellation touchpoint: CORA sends only the touchpoints the policy lists, then cancels with `CADENCE_EXPIRED` after `expireAfterLastTouchpoint`.

## Planned (TBD)

Not defined in these docs yet:

* **Engagement read endpoint** (MVP, not in the first wave). One read that tells CORA whether to engage for an application, resolving the global, property and household levels in one call. `agent.state_changed` carries `PROPERTY` scope today, and [`GET /agents/{agentKey}/state`](/api-reference/agents/get-agent-state) reads the property switch, so the engagement read covers only the household level.
* **Touchpoint metadata** (post-MVP). Recording the metadata of each touchpoint CORA sends, through a route separate from `POST /messages`.