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

# Read the document type definitions

GET https://api.fortresstech.io/v1/compliance-documents/manifest

Every document type CORA handles, each with its current definition (fields, mappings, rules), independent
of any application or upload. CORA reads it before any upload to build and review one Blueprint per
document type and definitionVersion.

The manifest and the
[application bundle](/api-reference/compliance-documents/get-application-bundle) share documentTypeId,
documentTypeKey and the definition objects (DefinitionField, DefinitionMapping, DefinitionRule). For a
given documentTypeId and definitionVersion, the bundle (with `include`) returns the definition this route
returned for that version. One version per document type. Any change to an approved definition is a new
definitionVersion. A definition that is not approved can change without a new version; the bundle serves
it as `definitionVersion: null`. This route returns only the current version. 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`. Fortress tells Fission Labs when a definition changes. There is no event for
it.

The response is a JSON array, one element per document type, with no envelope. The route takes no
parameters.

The API key needs the `compliance-documents: read` permission.

Authenticate with the `x-api-key` header.


Reference: https://developer.fortresstech.io/api-reference/compliance-documents/get-compliance-documents-manifest

## Authentication

- `x-api-key` header (required) — API Key authentication via header

## Response

### 200

Every document type with its current definition.

- `list of ManifestDocumentType`

## Errors

### 401 Unauthorized Error

No `x-api-key` header: API Gateway refuses the request before it reaches Fortress (body `{"message": "Unauthorized"}`). Also: Fortress refused a key that passed the gateway.

- `any`

### 403 Forbidden Error

The x-api-key is unknown (API Gateway), or the key does not have the `compliance-documents: read` permission (authorizer).

- `any`

## Types

### ManifestDocumentType

A document type and its current definition, independent of any application or upload. Same properties as a document type in the bundle (BundleDocumentType) without `documents` and `required`, plus `definitionStatus`. Here `label`, `fields`, `mappings` and `rules` are always returned; the bundle returns them, with `taxonomyDomain` and `recognition`, only with `include`. One definitionVersion per document type. Any change to an approved definition is a new definitionVersion. A definition that is not approved can change without a new version; the bundle serves it as `definitionVersion: null`.

- `documentTypeId` (string, required) — Fortress ID of the document type. Same value as in the bundle.
- `documentTypeKey` (string, required) — Stable key of the document type. Referenced by the authoring template and Fission's compiled definitions.
- `label` (string, required)
- `definitionVersion` (string, required) — Current version of the definition. 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`.
- `definitionStatus` (enum, required) — Authoring status of this definitionVersion (authoring template, tab 1). Fortress is still aligning with Fission Labs (CORA) on which statuses the bundle serves.
  - Allowed values: `draft`, `in_review`, `approved`, `retired`
- `fields` (list of DefinitionField, required)
- `mappings` (list of DefinitionMapping, required)
- `rules` (list of DefinitionRule, required)
- `taxonomyDomain` (string, optional)
- `recognition` (string, optional) — How to recognize this document type.

### DefinitionField

- `canonicalName` (string, required)
- `label` (string, required)
- `dataType` (string, required)
- `required` (boolean, required)
- `allowedValues` (list of string, optional, nullable)
- `instruction` (string, optional)

### DefinitionMapping

Compares a document value with the value Fortress holds for the application. The comparison itself (method, tolerance, what to do on a mismatch) is written in the document type's rules.

- `mappingId` (string, required)
- `documentValue` (string, required) — The `canonicalName` of a field on the same document type.
- `applicationSource` (string, required) — Where to read the value to compare: a path in `GET /applications/{applicationId}/full`, written as a reading guide (see that route for the notation). It replaces `applicationField`, which named a taxonomy concept rather than a place in a Fortress response.
- `status` (enum, required) — draft items are delivered but not run.
  - Allowed values: `active`, `draft`

### DefinitionRule

Self-contained: every value a rule needs (for example the 120-day recency window) is written into the rule itself. There are no shared settings. A rule needed by several document types is replicated in each definition; changing a value means a new definitionVersion for every document type that uses it.

- `ruleId` (string, required)
- `name` (string, required)
- `checkType` (enum, required)
  - Allowed values: `exact`, `ai_judgement`
- `ruleType` (enum, required)
  - Allowed values: `standard`, `fraud`
- `checksAcross` (enum, required)
  - Allowed values: `this_document`, `same_type_for_person`, `same_group_for_person`, `all_income_for_person`, `household`
- `onFail` (enum, required) — What CORA does when the check fails, and the document status it writes: - reupload -> REJECTED (the document no longer counts and the requirement goes back to AWAITING_UPLOAD when nothing else counts or it was the only counting document of a required type; the applicant uploads a new document and the review starts again). - clarification -> WAITING_FOR_APPLICANT while CORA waits for the applicant's clarification (in the message thread), then WAITING_FOR_COMPLIANCE when the clarification is ready for compliance. - compliance_only -> WAITING_FOR_COMPLIANCE. - note -> does not block; recorded for the reviewer. - fortress_decides -> CORA returns the computed figure; Fortress decides.
  - Allowed values: `reupload`, `clarification`, `compliance_only`, `note`, `fortress_decides`
- `evaluator` (enum, required)
  - Allowed values: `cora`, `fortress`
- `status` (enum, required) — draft items are delivered but not run.
  - Allowed values: `active`, `draft`
- `description` (string, optional)
- `program` (string, optional)
- `expression` (string, optional)
- `inputs` (list of string, optional)
- `applicantMessage` (string, optional, nullable)
- `reviewerMessage` (string, optional, nullable)

## Examples

**Response**

```json
[
  {
    "documentTypeId": "3b929155-899e-5fcf-ae13-c1f3ccab66ca",
    "documentTypeKey": "employment_pay_stub",
    "label": "Employment: Upload Pay stubs",
    "definitionVersion": "1.0",
    "definitionStatus": "approved",
    "fields": [
      {
        "canonicalName": "income.person.full_name",
        "label": "Employee Full Name",
        "dataType": "string",
        "required": true,
        "allowedValues": null,
        "instruction": "The employee's full legal name exactly as printed, usually in the top employee/earnings-statement block near the address. Include middle name and suffix if shown. Do not return the employer name."
      },
      {
        "canonicalName": "income.period.pay_date",
        "label": "Pay Date",
        "dataType": "date",
        "required": true,
        "allowedValues": null,
        "instruction": "The date the employee was actually paid, may be labeled Pay Date / Check Date / Deposit Date / Date Issued / Payment Date / Date of Payment. Reformat to YYYY-MM-DD. If both appear, this is the check/deposit date, separate from period end."
      },
      {
        "canonicalName": "income.period.frequency",
        "label": "Pay Frequency",
        "dataType": "string",
        "required": false,
        "allowedValues": [
          "daily",
          "weekly",
          "biweekly",
          "semi-monthly",
          "monthly"
        ],
        "instruction": "The pay cadence stated on the stub, normalized to exactly one of: daily, weekly, biweekly, semi-monthly, monthly. Only return if explicitly stated; do not infer from period length; else null."
      }
    ],
    "mappings": [
      {
        "mappingId": "M1",
        "documentValue": "income.person.full_name",
        "applicationSource": "applicants[].customer → firstName, middleName, lastName, suffix (adults); applicants[].minor → firstName, middleName, lastName, suffix (minors); applicants[].id is the bundle requirement's applicantId",
        "status": "active"
      },
      {
        "mappingId": "M4",
        "documentValue": "income.period.frequency",
        "applicationSource": "applicants[].incomes[] where type = JOB and payComponent = BASE_PAY → payFrequency",
        "status": "active"
      }
    ],
    "rules": [
      {
        "ruleId": "I.1",
        "name": "Legibility",
        "checkType": "ai_judgement",
        "ruleType": "standard",
        "checksAcross": "household",
        "onFail": "reupload",
        "evaluator": "cora",
        "status": "active",
        "description": "Document is legible- nothing is cut off or blurry",
        "program": "All",
        "inputs": [],
        "applicantMessage": "Looks like {document} is blurry/cut off. Can you please upload a clear version of this document with nothing cut off?",
        "reviewerMessage": null
      },
      {
        "ruleId": "I.4",
        "name": "Gross = Net + Deductions",
        "checkType": "exact",
        "ruleType": "fraud",
        "checksAcross": "this_document",
        "onFail": "compliance_only",
        "evaluator": "cora",
        "status": "active",
        "description": "Current gross pay equals current net pay plus current total deductions",
        "program": "All",
        "expression": "abs(income.current.gross_pay - (income.current.net_pay + income.current.total_deductions)) <= 1.00",
        "inputs": [
          "income.current.gross_pay",
          "income.current.net_pay",
          "income.current.total_deductions"
        ],
        "applicantMessage": "CORA is digging deeper",
        "reviewerMessage": null
      },
      {
        "ruleId": "I.7",
        "name": "Pay period is recent",
        "checkType": "exact",
        "ruleType": "standard",
        "checksAcross": "this_document",
        "onFail": "reupload",
        "evaluator": "cora",
        "status": "active",
        "description": "The pay period end date is within 120 days of the certification effective date.",
        "program": "All",
        "expression": "within_days(income.period.end_date, application.certification_effective_date, 120)",
        "inputs": [
          "income.period.end_date",
          "application.certification_effective_date"
        ],
        "applicantMessage": "Please upload a recent pay stub. The one we have is more than 120 days old.",
        "reviewerMessage": null
      }
    ],
    "taxonomyDomain": "Income",
    "recognition": "An earnings statement issued by an employer for a specific pay period. Shows the employee and employer names, pay period and/or pay date, current and year-to-date gross pay, deductions and net pay. May be a paper check stub or a direct-deposit advice."
  },
  {
    "documentTypeId": "9d74c315-593a-581a-8739-a3783de916f3",
    "documentTypeKey": "identity_social_security_card",
    "label": "Identity Verification: Upload Social Security Card",
    "definitionVersion": "1.0",
    "definitionStatus": "in_review",
    "fields": [
      {
        "canonicalName": "identity.ssn_card.name_on_card",
        "label": "Name on Card",
        "dataType": "string",
        "required": true,
        "allowedValues": null,
        "instruction": "The name printed on the card. Return as printed."
      },
      {
        "canonicalName": "identity.ssn_card.card_restriction",
        "label": "Card Restriction",
        "dataType": "string",
        "required": false,
        "allowedValues": [
          "none",
          "not_valid_for_employment",
          "valid_with_dhs_authorization"
        ],
        "instruction": "Any restriction printed on the card. Return one listed word."
      }
    ],
    "mappings": [],
    "rules": [
      {
        "ruleId": "I.1",
        "name": "Legibility",
        "checkType": "ai_judgement",
        "ruleType": "standard",
        "checksAcross": "household",
        "onFail": "reupload",
        "evaluator": "cora",
        "status": "active",
        "description": "Document is legible- nothing is cut off or blurry",
        "program": "All",
        "inputs": [],
        "applicantMessage": "Looks like {document} is blurry/cut off. Can you please upload a clear version of this document with nothing cut off?",
        "reviewerMessage": null
      },
      {
        "ruleId": "C.2",
        "name": "Name matches a household member",
        "checkType": "ai_judgement",
        "ruleType": "standard",
        "checksAcross": "household",
        "onFail": "clarification",
        "evaluator": "cora",
        "status": "active",
        "description": "The name on the document matches a household member on the application. Allow middle names, maiden or married names, nicknames, suffixes, more than one surname, and different name order. If the only difference is a last name change (for example after marriage or divorce), ask about it. Look at the application and the household's other uploaded documents. Ask only if it is not accounted for in either.",
        "program": "All",
        "inputs": [],
        "applicantMessage": "The name on your Social Security card is different from the name on your application. Please explain, for example a name change after marriage or divorce.",
        "reviewerMessage": null
      },
      {
        "ruleId": "C.5",
        "name": "Card restriction",
        "checkType": "exact",
        "ruleType": "standard",
        "checksAcross": "this_document",
        "onFail": "note",
        "evaluator": "cora",
        "status": "active",
        "description": "If Card Restriction is not none, note it for the reviewer.",
        "program": "All",
        "inputs": [],
        "applicantMessage": null,
        "reviewerMessage": null
      }
    ],
    "taxonomyDomain": "Identity & Composition",
    "recognition": "A Social Security card issued by the U.S. Social Security Administration, showing the person's name and Social Security number. Some cards say 'Not valid for employment' or 'Valid for work only with DHS authorization'. A letter from Social Security showing the number may also be uploaded here. For a newborn, proof the number was applied for may be uploaded until the card arrives. No expiration date."
  }
]
```

**SDK Code**

```python Two document types (trimmed)
import requests

url = "https://api.fortresstech.io/v1/compliance-documents/manifest"

headers = {"x-api-key": "<apiKey>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript Two document types (trimmed)
const url = 'https://api.fortresstech.io/v1/compliance-documents/manifest';
const options = {method: 'GET', headers: {'x-api-key': '<apiKey>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Two document types (trimmed)
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.fortresstech.io/v1/compliance-documents/manifest"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("x-api-key", "<apiKey>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Two document types (trimmed)
require 'uri'
require 'net/http'

url = URI("https://api.fortresstech.io/v1/compliance-documents/manifest")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<apiKey>'

response = http.request(request)
puts response.read_body
```

```java Two document types (trimmed)
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.fortresstech.io/v1/compliance-documents/manifest")
  .header("x-api-key", "<apiKey>")
  .asString();
```

```php Two document types (trimmed)
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.fortresstech.io/v1/compliance-documents/manifest', [
  'headers' => [
    'x-api-key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp Two document types (trimmed)
using RestSharp;

var client = new RestClient("https://api.fortresstech.io/v1/compliance-documents/manifest");
var request = new RestRequest(Method.GET);
request.AddHeader("x-api-key", "<apiKey>");
IRestResponse response = client.Execute(request);
```

```swift Two document types (trimmed)
import Foundation

let headers = ["x-api-key": "<apiKey>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.fortresstech.io/v1/compliance-documents/manifest")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```