Case configurations

List your organization's case configurations and read what each one expects: the fields it collects, the documents it requires and who provides them, its participants and checks, and the JSON Schema of the dossier payload. Use it to build your upload flow before any dossier exists.

Overview

Both endpoints are read-only and free. The catalogue is the machine-readable version of the Show input schema modal on the dossier configuration screen, so your integration reads what a colleague sees there.

The detail is the template of the dossier: it uses the same nesting as GET /cases/{case_id} does, with the same keys, and each row is the dossier's row minus its current state. The configuration's checks are listed under rules with the same stable code their verdicts carry on the dossier.

Neither endpoint takes query parameters. An unknown parameter is refused (UNKNOWN_PARAMETER) rather than ignored.

List case configurations

GET /api/v1.1/case-configs returns every configuration of your organization, ordered by name. config_id is the one thing you need from this call: send it as config_id on POST /cases to create a dossier under that configuration.

List case configurations

curl https://app.holofin.ai/api/v1.1/case-configs \
  -H "X-API-Key: $HOLOFIN_API_KEY"
{
  "configs": [
    {"config_id": "0b7e2d9c-6a41-4f3b-8e5d-1c9a7f2b6d03",
     "name": "Bail habitation"},
    {"config_id": "e5b1c9a0-4f2d-4b8e-9c3a-7d6f1e0a8b42",
     "name": "Crédit pro"}
  ]
}

Read one configuration

GET /api/v1.1/case-configs/{config_id} returns the configuration's full contract in the shape of the dossier it produces: the fields it collects, the requirements it expects at dossier level, the participants it declares with the fields and requirements each of them carries, the rules it checks, and the JSON Schema of the POST /api/v1.1/cases body. Every key of a row is always present, null or false when the configuration does not set it.

curl https://app.holofin.ai/api/v1.1/case-configs/e5b1c9a0-4f2d-4b8e-9c3a-7d6f1e0a8b42 \
  -H "X-API-Key: $HOLOFIN_API_KEY"

The example below is read with rule details enabled; without them required_if, activation.condition, rule and description answer null.

{
  "config_id": "e5b1c9a0-4f2d-4b8e-9c3a-7d6f1e0a8b42",
  "name": "Crédit pro",
  "fields": {
    "montant": {"label": "Montant", "type": "number", "required": true,
                "conditional": false, "required_if": null, "choices": null},
    "produit": {"label": "Produit", "type": "enum", "required": false,
                "conditional": false, "required_if": null,
                "choices": ["pret_pro", "credit_bail"]},
    "apport":  {"label": "Apport", "type": "number", "required": false,
                "conditional": true, "required_if": "@dossier.montant > 100000",
                "choices": null}
  },
  "requirements": [
    {"key": "releve_bancaire", "label": "Bank statement", "description": null,
     "accepted_categories": ["relevé_bancaire"], "min": 2, "max": null,
     "conditional": false, "activation": null,
     "shareable": false, "presence_only": false, "group": null},
    {"key": "kbis", "label": "KBIS",
     "description": "Registration extract of the financed company",
     "accepted_categories": ["kbis"], "min": 1, "max": null,
     "conditional": true,
     "activation": {"condition": "@emprunteur.party_type == 'PM'"},
     "shareable": false, "presence_only": false, "group": null}
  ],
  "participants": {
    "emprunteur": {
      "label": "Emprunteur", "min": 1, "max": 2,
      "fields": {
        "firstname": {"label": "First name", "type": "text", "required": true,
                      "conditional": false, "required_if": null, "choices": null},
        "siren":     {"label": "SIREN", "type": "siren", "required": false,
                      "conditional": false, "required_if": null, "choices": null}
      },
      "requirements": [
        {"key": "justificatif_identite", "label": "Pièce d'identité",
         "description": "Identity proof of the borrower",
         "accepted_categories": ["pièce_identité"], "min": 1, "max": 2,
         "conditional": false, "activation": null,
         "shareable": true, "presence_only": false, "group": null}
      ]
    }
  },
  "rules": [
    {"code": "case_rule_9d81b2c7e4", "label": "Relevé de moins de 3 mois",
     "kind": "validity", "severity": "blocking",
     "depends_on": [{"type": "requirement", "key": "releve_bancaire"}],
     "agent_review": false,
     "rule": "@releve_date > TODAY - 3 MONTH", "description": null},
    {"code": "case_rule_a3f19c04d2", "label": "Capital social suffisant",
     "kind": "consistency", "severity": "blocking",
     "depends_on": [{"type": "requirement", "key": "kbis"},
                    {"type": "dossier", "key": "dossier"}],
     "agent_review": true,
     "rule": "ASSERT @case.documents.kbis.capital >= @case.montant ON KO REVIEW WITH AGENT \"…\"",
     "description": "Checks that the company's share capital covers the amount requested."},
    {"code": "case_rule_5c40aa91f8", "label": "Titulaire du relevé",
     "kind": "consistency", "severity": "warning",
     "depends_on": [{"type": "requirement", "key": "releve_bancaire"},
                    {"type": "participant", "key": "emprunteur"}],
     "agent_review": false,
     "rule": "WARN IF @case.emprunteur.documents.releve_bancaire.titulaire != @case.emprunteur.lastname",
     "description": null}
  ],
  "payload_schema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "title": "Crédit pro",
    "type": "object",
    "additionalProperties": true,
    "properties": {
      "case_id": {
        "type": "string",
        "description": "Your dossier reference. Omit it and Holofin generates one."
      },
      "case_fields": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "montant": {"type": ["number", "null"], "description": "Montant"},
          "produit": {"enum": ["pret_pro", "credit_bail", null],
                      "description": "Produit"},
          "apport": {"type": ["number", "null"], "description": "Apport — conditional"},
          "emprunteur": {
            "anyOf": [{"$ref": "#/$defs/emprunteur"},
                      {"type": "array", "items": {"$ref": "#/$defs/emprunteur"}}],
            "description": "Between 1 and 2 participants.",
            "x-participant-cardinality": {"min": 1, "max": 2}
          }
        },
        "x-required-for-completeness": ["montant", "apport"]
      }
    },
    "$defs": {
      "emprunteur": {
        "title": "Emprunteur",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "party_ref": {"type": "string",
            "description": "Optional: your own reference for this participant, defaults to the field key"},
          "party_type": {"enum": ["PP", "PPNS", "PM", "BE", null],
            "description": "Optional: the participant's legal nature. Rules read it as @<key>.party_type"},
          "firstname": {"type": ["string", "null"], "description": "First name"},
          "siren": {"type": ["string", "null"], "pattern": "^\\d{9}$",
                    "description": "SIREN"}
        },
        "x-required-for-completeness": ["firstname"]
      }
    }
  }
}

Read it as the template of GET /api/v1.1/cases/{case_id}. On the dossier, under the same keys:

  • a field entry becomes {value, state, required};
  • a requirement row gains its state, counts and documents;
  • a participant wrapper gains its parties, each carrying an instance of the fields and requirements listed here;
  • a rule gains its verdict under the same code.

Nothing is listed twice: a requirement provided by a participant appears under that participant and nowhere else.

Fields

Declared input fields, keyed by the key you send: fields at the root holds the direct keys of case_fields, and each participant's fields holds the keys of that participant's object. Same keys, same nesting as the dossier's fields.

KeyTypeDescription
labelstringDisplay label.
typestringThe declared type the value is validated against: text, number, boolean, date, enum, siren, address or iso_country.
requiredbooleanWhether the dossier always needs this value to be complete. It never means one call must carry it: values merge across calls. On the dossier, required is this flag resolved as of the last evaluation, conditions included.
conditional / required_ifboolean / stringconditional says the field becomes required when a configured condition holds; required_if is that condition as written, null on a plain field and null unless your organization has rule details enabled.
choicesarrayThe allowed values of an enum field, null for every other type. Sending anything else is a 400 (INVALID_INPUT_FIELD).

Requirements

One row per expected document, listed where its instances will appear on the dossier: at the root for a dossier-level requirement, under the participant that provides it otherwise. The row is the dossier's requirement row without its state: key, label, accepted_categories, min, max, conditional and activation read the same on both.

KeyTypeDescription
keystringThe requirement's identifier, the same one the dossier response uses.
labelstringDisplay label. When the configuration authors none, it falls back to the accepted categories' human names.
descriptionstringThe configuration's own explanation of why the document is required; null when none is authored.
accepted_categoriesarrayThe document categories that satisfy the requirement, the vocabulary a document row's category answers in.
min / maxintegerHow many documents are required, and the cap; max is null when uncapped.
conditional / activationboolean / objectA plain requirement reads conditional: false and activation: null. A conditional one applies only when a configured condition holds and carries activation: {"condition"}, the condition as written (several lines mean any one of them activates); condition is null unless your organization has rule details enabled. The dossier's row adds active, whether the condition currently holds.
shareablebooleanA document may satisfy this requirement even when already counted under another; on the dossier its row then appears in both places, shared: true on the copy that consumes nothing.
presence_onlybooleanDocuments count once classified, whatever their extraction state.
groupstringThe name several requirements share when a rule reads them together, null otherwise. It is what a rule's depends_on may name instead of the requirement's own key.

Participants

Keyed by participant key, the key of case_fields that carries this participant's values on POST /cases and the key the dossier's participants uses. Each wrapper declares a type of participant and how many of it the dossier expects, never one named person: «emprunteur» may be one or two people. The count is checked for completeness, not when the request is validated: a dossier holding fewer stays INCOMPLETE until the rest arrive.

KeyTypeDescription
labelstringDisplay label.
min / maxintegerHow many participants of this type the dossier expects. max is null when uncapped. The dossier's wrapper adds provided and its state.
fieldsobjectThe fields each participant of this type carries, same entry shape as the root fields. On the dossier they appear under each party in parties[].
requirementsarrayThe documents each participant of this type provides, same row shape as the root requirements. The dossier holds one instance per party.

Rules

One flat list of the configuration's checks, wherever they attach: the same rows the dossier's rules answers, minus the verdict. Join a verdict to its definition on code: it is derived from the rule itself and stays the same across dossiers and evaluations, and changes only when the rule is edited.

KeyTypeDescription
codestringThe check's stable identifier, the one its verdict carries on every dossier under this configuration.
labelstringThe name the configuration gives the check, the one shown in the Holofin app; null for a rule written without one.
kindstringvalidity (judges one requirement's documents; a document failing it reads state: "invalid" under that requirement) or consistency (compares values across documents and declared data), the two sections of the configuration. A conditional requirement's activation is not a rule entry: it lives on the requirement's own activation.
severitystringblocking or warning. A blocking failure holds the dossier; a warning is reported and blocks nothing.
depends_onarrayWhat the check reads, each as {"type": "requirement" | "participant" | "dossier", "key"}. A validity check names the requirement it judges first. A check reading a participant runs once per person filling it on the dossier. The dossier's entries add party_ref and state.
agent_reviewbooleanWhether a failure is adjudicated by Holofin's review agent before it stands, so a ko on the dossier may be overturned with a rationale.
rule / descriptionstringThe rule's text, and what the check verifies in plain language on the rules whose configuration asks for one. Both null unless your organization has rule details enabled.

Payload schema

payload_schema is a JSON Schema (draft 2020-12) for the POST /api/v1.1/cases body, the same document the configuration page's Show input schema modal prints. Feed it to your own validator or a form generator. The sections above and the schema are built from the same declarations, so they cannot disagree.

The schema never enforces counts: no required field keys, no minItems or maxItems, because values merge across calls and a legal partial call must not fail your validator. The completeness contract rides in x-required-for-completeness, and participant bounds in x-participant-cardinality. Every field type also accepts null, which clears the key.

Errors

CodeStatusMeaning
APPLICATIONS_DISABLED400The Dossiers feature is not enabled for your organization. Same code and status the dossier endpoints answer.
UNKNOWN_PARAMETER400A query parameter was sent. These endpoints take none, and refuse rather than ignore.
NOT_FOUND404No case configuration with that config_id in your organization. A configuration belonging to another organization and one that does not exist answer identically, and a malformed id is the same plain 404.