11.6 Finance applications
Facility finance applications are submitted against an authoritative product version, move through a lifecycle, and reach a committee decision.
Lifecycle values: DRAFT, SUBMITTED, ELIGIBILITY_REVIEW, DUE_DILIGENCE, APPRAISAL, COMMITTEE_REVIEW, APPROVED, DECLINED, WITHDRAWN, EXPIRED. The allowed transitions are defined by canTransitionApplication and are not repeated here.
Financing instruments: DEBT, PPP_FINANCE, GUARANTEE, TRADE_FINANCE, EQUITY, VENTURE_CAPITAL.
GET /api/v1/finance-applications
- Purpose. Lists finance applications visible to the actor.
- Authentication. Session cookie and
X-DFFMS-Tenant-Id. - Role. Finance read set: PSA, CSA, PA, SCA, IA, FEO, FA, AC, EDV, IV. Results are filtered by the application data scope.
- Parameters.
limit,cursor(see Section 5). - Response. Same shape as the generic list.
idempotencyKeyis redacted in list responses. - Errors. As for generic list (400, 401, 403).
POST /api/v1/finance-applications
- Purpose. Submits a new finance application linked to a published product version. Applicant eligibility is evaluated at submission.
- Authentication. Session cookie,
X-DFFMS-Tenant-Id,Origin,Content-Type: application/json,Idempotency-Keyheader. - Role.
createonfinance_application: CSA, PA, SCA, IA, FEO, FA (or PSA). No step-up. - Body.
| Field | Type | Required | Rules |
|---|---|---|---|
productVersionId | UUID | Yes | Must be a published, non-superseded version |
windowId | UUID | No | Must be an active product window |
stateId | UUID | No | State of operation |
instrument | enum | Yes | DEBT, PPP_FINANCE, GUARANTEE, TRADE_FINANCE, EQUITY, VENTURE_CAPITAL |
ticketCode | string | Yes | ^[A-Z0-9_]{2,64}$ |
applicantCategory | string | Yes | Trimmed; 2 to 120 |
intendedActivity | string | Yes | Trimmed; 10 to 500 |
requestedAmountMinor | string | Yes | Digits only (^\d+$); must be greater than zero |
currency | string | No | ISO 4217 uppercase; default NGN |
Unknown fields are ignored (the schema strips them).
curl -s -X POST https://dffms.org/api/v1/finance-applications \
-H 'Cookie: __Secure-dffms.session_token=<session-cookie>' \
-H 'X-DFFMS-Tenant-Id: <tenantId>' \
-H 'Origin: https://dffms.org' \
-H 'Idempotency-Key: fa-2026-10-26-0001' \
-H 'Content-Type: application/json' \
-d '{
"productVersionId": "0190f2c4-6b3e-7a1d-8c2f-000000000030",
"stateId": "0190f2c4-6b3e-7a1d-8c2f-000000000031",
"instrument": "DEBT",
"ticketCode": "SMALLHOLDER_INPUTS",
"applicantCategory": "Smallholder cooperative",
"intendedActivity": "Purchase of certified seed and fertiliser for the 2027 season.",
"requestedAmountMinor": "500000000",
"currency": "NGN"
}'
Response (201): the stored application row (id, tenantId, reference such as DFFMS-APP-YYYYMMDD-XXXXXXXX, lifecycle set to SUBMITTED, eligibilitySnapshot, idempotencyKey, timestamps).
Errors:
| HTTP | Code | Meaning |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | Header missing or malformed. |
| 400 | VALIDATION_ERROR | Field errors (fields), tenant header problems, origin problems. |
| 400 | INVALID_AMOUNT | Requested amount is zero. |
| 400 | PRODUCT_VERSION_UNAVAILABLE | Product version not published or not in the authoritative register. |
| 400 | PRODUCT_VERSION_SUPERSEDED | Version replaced by a later term sheet; apply under the current version. |
| 400 | WINDOW_UNAVAILABLE | Window not active. |
| 400 | STATE_UNAVAILABLE | State not valid for the tenant. |
| 400 | ELIGIBILITY_FAILED | Blocking eligibility rule failed. fields maps rule codes to messages. |
| 401 | UNAUTHENTICATED | No session. |
| 403 | FORBIDDEN | Role cannot create applications. |
| 403 | MFA_REQUIRED | Tenant requires MFA enrolment. |
| 409 | IDEMPOTENCY_CONFLICT | Key reused with a different request. |
| 500 | INTERNAL_ERROR | Unhandled error. |
Side effects: inserts the application (reference DFFMS-APP-<date>-<8 hex>), an eligibility snapshot, and a submission audit event FINANCE_APPLICATION_SUBMITTED. Queues notifications to application team roles (PROGRAMME_ADMIN, STATE_COORDINATOR_ADMIN, INSTITUTION_ADMIN, FINANCE_ENTRY_OFFICER) and related escalation events.
POST /api/v1/finance-applications/{applicationId}/transition
- Purpose. Moves an application through a non-committee lifecycle step (for example
SUBMITTEDtoELIGIBILITY_REVIEW). - Authentication. Session cookie,
X-DFFMS-Tenant-Id,Origin,Idempotency-Keyheader. - Role.
updateonfinance_application(CSA, PA, SCA, IA, FEO, FA) plus saved role policy check forfinance:update. No step-up. - Path.
applicationId: UUID. - Body.
| Field | Type | Required | Rules |
|---|---|---|---|
expectedLifecycle | enum | Yes | Current lifecycle (optimistic check) |
targetLifecycle | enum | Yes | Must not be APPROVED or DECLINED |
reason | string | Yes | Trimmed; 10 to 1,000 |
Unknown fields are ignored.
curl -s -X POST https://dffms.org/api/v1/finance-applications/0190f2c4-6b3e-7a1d-8c2f-000000000040/transition \
-H 'Cookie: __Secure-dffms.session_token=<session-cookie>' \
-H 'X-DFFMS-Tenant-Id: <tenantId>' \
-H 'Origin: https://dffms.org' \
-H 'Idempotency-Key: fa-trans-2026-10-26-0001' \
-H 'Content-Type: application/json' \
-d '{"expectedLifecycle":"SUBMITTED","targetLifecycle":"ELIGIBILITY_REVIEW","reason":"Submitted with complete documents; starting eligibility review."}'
Response (200): the application row after the transition.
Errors:
| HTTP | Code | Meaning |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | Header missing or malformed. |
| 400 | VALIDATION_ERROR | Body or path invalid. |
| 401 | UNAUTHENTICATED | No session. |
| 403 | FORBIDDEN | Role or saved policy does not permit. |
| 404 | NOT_FOUND | Missing, or outside the actor's scope (not distinguished). |
| 409 | COMMITTEE_DECISION_REQUIRED | Target is APPROVED or DECLINED; use /decision. |
| 409 | INVALID_STATE_TRANSITION | Transition not allowed by the lifecycle rules. |
| 409 | STALE_STATE | expectedLifecycle no longer matches. |
Side effects: updates lifecycle, may open a pending approval row for committee review, writes a transition event and audit FINANCE_APPLICATION_TRANSITIONED. Replays with the same key return the current application without checking the body.
POST /api/v1/finance-applications/{applicationId}/decision
- Purpose. Records the independent committee decision on an application in
COMMITTEE_REVIEW. Approval records the exact amount and final terms; soft rule failures can be carried as rule exceptions. - Authentication. Session cookie,
X-DFFMS-Tenant-Id,Origin,Idempotency-Keyheader, MFA enrolled, session age within 15 minutes. - Role.
approveonfinance_application: FA or SCA (or PSA), plus saved policyfinance:approve. - Path.
applicationId: UUID. - Body.
| Field | Type | Required | Rules |
|---|---|---|---|
decision | enum | Yes | APPROVED or DECLINED |
reason | string | Yes | Trimmed; 10 to 1,000 |
approvedAmountMinor | string | Required for APPROVED | Digits only; greater than zero |
approvedTerms | object | Required for APPROVED | Shape depends on instrument (below) |
Credit terms (DEBT, PPP_FINANCE, GUARANTEE, TRADE_FINANCE):
| Field | Type | Rules |
|---|---|---|
pricingStructure | string | ^[A-Z0-9_]{2,64}$ |
interestRateBps | integer | 0 to 2,000 |
tenorMonths | integer | 1 to 300 |
graceMonths | integer | 0 to 60 |
contributionBps | integer | 0 to 10,000, optional |
coverageBps | integer | 0 to 10,000, optional |
repaymentStructure | string | 3 to 240 |
conditions | string[] | Up to 50 items, each 3 to 500; default [] |
Equity terms (EQUITY, VENTURE_CAPITAL):
| Field | Type | Rules |
|---|---|---|
ownershipBps | integer | 1 to 10,000 |
valuationMinor | string | Digits, optional |
milestones | string[] | 1 to 50 items, each 3 to 500 |
exitPlan | string | 3 to 1,000, optional |
conditions | string[] | Up to 50 items, each 3 to 500; default [] |
curl -s -X POST https://dffms.org/api/v1/finance-applications/0190f2c4-6b3e-7a1d-8c2f-000000000040/decision \
-H 'Cookie: __Secure-dffms.session_token=<session-cookie>' \
-H 'X-DFFMS-Tenant-Id: <tenantId>' \
-H 'Origin: https://dffms.org' \
-H 'Idempotency-Key: fa-decision-2026-10-26-0001' \
-H 'Content-Type: application/json' \
-d '{
"decision": "APPROVED",
"reason": "Committee approved the request with the conditions below.",
"approvedAmountMinor": "450000000",
"approvedTerms": {
"pricingStructure": "<pricingStructure>",
"interestRateBps": 1200,
"tenorMonths": 36,
"graceMonths": 6,
"repaymentStructure": "Quarterly amortising repayments after grace",
"conditions": ["Insurance cover bound before first drawdown"]
}
}'
Response (200): the application row after the decision.
Errors:
| HTTP | Code | Meaning |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | Header missing or malformed. |
| 400 | VALIDATION_ERROR | Body invalid, or approvedTerms does not match the instrument shape. |
| 400 | APPROVED_TERMS_REQUIRED | Approval without amount or terms. (Returned with the default status 400.) |
| 400 | INVALID_AMOUNT | Approved amount zero or negative. |
| 400 | FINAL_TERMS_INVALID | A hard rule failed. fields lists all failures with committeeExceptionAllowed. |
| 401 | UNAUTHENTICATED | No session. |
| 403 | MFA_REQUIRED | No MFA enrolment. |
| 403 | FRESH_AUTH_REQUIRED | Session older than 15 minutes. |
| 403 | FORBIDDEN | Role or saved policy does not permit. |
| 403 | MAKER_CHECKER_REQUIRED | Decider is the requester of the approval or the applicant. Applies to PSA too. |
| 404 | NOT_FOUND | Missing or outside scope. |
| 409 | INVALID_STATE | Application not in COMMITTEE_REVIEW. |
| 409 | APPROVAL_NOT_FOUND | No pending committee approval. |
| 409 | STALE_STATE | Application changed before the decision was recorded. |
Side effects: records the decision, audit FINANCE_APPLICATION_APPROVED or FINANCE_APPLICATION_DECLINED, resolves escalations, and queues notifications to the decider roles and applicant team. Soft failures create product rule exceptions in state PENDING for later review. Replays with the same key return the current application.