Skip to main content

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. idempotencyKey is 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-Key header.
  • Role. create on finance_application: CSA, PA, SCA, IA, FEO, FA (or PSA). No step-up.
  • Body.
FieldTypeRequiredRules
productVersionIdUUIDYesMust be a published, non-superseded version
windowIdUUIDNoMust be an active product window
stateIdUUIDNoState of operation
instrumentenumYesDEBT, PPP_FINANCE, GUARANTEE, TRADE_FINANCE, EQUITY, VENTURE_CAPITAL
ticketCodestringYes^[A-Z0-9_]{2,64}$
applicantCategorystringYesTrimmed; 2 to 120
intendedActivitystringYesTrimmed; 10 to 500
requestedAmountMinorstringYesDigits only (^\d+$); must be greater than zero
currencystringNoISO 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:

HTTPCodeMeaning
400IDEMPOTENCY_KEY_REQUIREDHeader missing or malformed.
400VALIDATION_ERRORField errors (fields), tenant header problems, origin problems.
400INVALID_AMOUNTRequested amount is zero.
400PRODUCT_VERSION_UNAVAILABLEProduct version not published or not in the authoritative register.
400PRODUCT_VERSION_SUPERSEDEDVersion replaced by a later term sheet; apply under the current version.
400WINDOW_UNAVAILABLEWindow not active.
400STATE_UNAVAILABLEState not valid for the tenant.
400ELIGIBILITY_FAILEDBlocking eligibility rule failed. fields maps rule codes to messages.
401UNAUTHENTICATEDNo session.
403FORBIDDENRole cannot create applications.
403MFA_REQUIREDTenant requires MFA enrolment.
409IDEMPOTENCY_CONFLICTKey reused with a different request.
500INTERNAL_ERRORUnhandled 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 SUBMITTED to ELIGIBILITY_REVIEW).
  • Authentication. Session cookie, X-DFFMS-Tenant-Id, Origin, Idempotency-Key header.
  • Role. update on finance_application (CSA, PA, SCA, IA, FEO, FA) plus saved role policy check for finance:update. No step-up.
  • Path. applicationId: UUID.
  • Body.
FieldTypeRequiredRules
expectedLifecycleenumYesCurrent lifecycle (optimistic check)
targetLifecycleenumYesMust not be APPROVED or DECLINED
reasonstringYesTrimmed; 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:

HTTPCodeMeaning
400IDEMPOTENCY_KEY_REQUIREDHeader missing or malformed.
400VALIDATION_ERRORBody or path invalid.
401UNAUTHENTICATEDNo session.
403FORBIDDENRole or saved policy does not permit.
404NOT_FOUNDMissing, or outside the actor's scope (not distinguished).
409COMMITTEE_DECISION_REQUIREDTarget is APPROVED or DECLINED; use /decision.
409INVALID_STATE_TRANSITIONTransition not allowed by the lifecycle rules.
409STALE_STATEexpectedLifecycle 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-Key header, MFA enrolled, session age within 15 minutes.
  • Role. approve on finance_application: FA or SCA (or PSA), plus saved policy finance:approve.
  • Path. applicationId: UUID.
  • Body.
FieldTypeRequiredRules
decisionenumYesAPPROVED or DECLINED
reasonstringYesTrimmed; 10 to 1,000
approvedAmountMinorstringRequired for APPROVEDDigits only; greater than zero
approvedTermsobjectRequired for APPROVEDShape depends on instrument (below)

Credit terms (DEBT, PPP_FINANCE, GUARANTEE, TRADE_FINANCE):

FieldTypeRules
pricingStructurestring^[A-Z0-9_]{2,64}$
interestRateBpsinteger0 to 2,000
tenorMonthsinteger1 to 300
graceMonthsinteger0 to 60
contributionBpsinteger0 to 10,000, optional
coverageBpsinteger0 to 10,000, optional
repaymentStructurestring3 to 240
conditionsstring[]Up to 50 items, each 3 to 500; default []

Equity terms (EQUITY, VENTURE_CAPITAL):

FieldTypeRules
ownershipBpsinteger1 to 10,000
valuationMinorstringDigits, optional
milestonesstring[]1 to 50 items, each 3 to 500
exitPlanstring3 to 1,000, optional
conditionsstring[]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:

HTTPCodeMeaning
400IDEMPOTENCY_KEY_REQUIREDHeader missing or malformed.
400VALIDATION_ERRORBody invalid, or approvedTerms does not match the instrument shape.
400APPROVED_TERMS_REQUIREDApproval without amount or terms. (Returned with the default status 400.)
400INVALID_AMOUNTApproved amount zero or negative.
400FINAL_TERMS_INVALIDA hard rule failed. fields lists all failures with committeeExceptionAllowed.
401UNAUTHENTICATEDNo session.
403MFA_REQUIREDNo MFA enrolment.
403FRESH_AUTH_REQUIREDSession older than 15 minutes.
403FORBIDDENRole or saved policy does not permit.
403MAKER_CHECKER_REQUIREDDecider is the requester of the approval or the applicant. Applies to PSA too.
404NOT_FOUNDMissing or outside scope.
409INVALID_STATEApplication not in COMMITTEE_REVIEW.
409APPROVAL_NOT_FOUNDNo pending committee approval.
409STALE_STATEApplication 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.