Skip to navigation
AI Team MembersCORA

CORA

The document-review agent in the Fortress integration

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 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 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 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, 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.
    • 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 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 walks through one application from signature to completion. The API Reference lists every route CORA calls.

Statuses

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

StatusWritten byMeaning
NOT_YET_REVIEWEDFortressUploaded, not processed yet.
EXTRACTINGCORAQuality checks, then reading fields.
EVALUATING_RULESCORARequired fields, rules and mappings.
WAITING_FOR_APPLICANTCORA or complianceWaiting 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_COMPLIANCECORA, 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_CORACORAEvery check passed.
APPROVEDFortress onlyApproved by a human.
REJECTEDCORA or staffCORA (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 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.

Events are not authenticated by the API key: each delivery is signed with the X-Fortress-* headers. See Webhooks.

Cadence policy

The timing of CORA’s nudges comes from cadencePolicy on GET /agents/cora/state?propertyId=, 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. An applicant response or upload (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.

More in 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 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.