API Integration Guide
For developers integrating directly with the DirecFunds Experience API — for example, as part of a SoftPro Banking Module integration or a custom title software workflow.
Overview
The DirecFunds Experience API is a RESTful API using OAuth 2.0 for authentication. All requests must include a bearer token obtained from the DirecFunds identity server.
| Environment | Base URL |
|---|---|
| Production | https://api.direcfunds.com/ |
| Test | https://testapi.direcfunds.com/ |
Authentication
Step 1 — Generate API Credentials
Log in to the DirecFunds portal as an administrative user. Click the gear icon, then Manage API Integration, and toggle the integration on. Your Client ID and Client Secret will be shown. Store both securely.
Step 2 — Request a Bearer Token
Send a POST request to the token endpoint using your credentials as a multipart form body:
POST https://direcfundstest.b2clogin.com/direcfundstest.onmicrosoft.com/B2C_1A_CLIENT_CREDENTIALS/oauth2/v2.0/token
grant_type=client_credentials
client_id=YOUR_CLIENT_ID
client_secret=YOUR_CLIENT_SECRET
scope=https://direcfundstest.onmicrosoft.com/public/.default
A successful response returns an access_token along with expires_in (3600 seconds). Tokens are valid for 1 hour. Request a new one before it expires — do not wait for a 401 response.
{
"access_token": "eyJ0eXAiOi...",
"token_type": "Bearer",
"not_before": 1765842893,
"expires_in": 3600,
"expires_on": 1765846493,
"resource": "00000000-0000-0000-0000-000000000000"
}
Step 3 — Include the Token on Every Request
Pass the token in the Authorization header on every API call:
Authorization: bearer eyJ0eXAiOi...
Object Hierarchy
Before integrating, understand how DirecFunds objects relate to each other:
Company
├── Branch (one or more office locations)
│ ├── Bank Account (source of funds, linked to branches)
│ └── User (employees with disbursement permissions)
├── Address (used by Branch and Bank Account)
├── Phone Number (optional, attached to Branch)
└── Disbursement
├── Pay In (draw-down from source account)
│ └── Target Account (receiving account for the draw-down)
└── Pay Out (transfer to each recipient)
└── Target Account (recipient's bank account)
A Disbursement is the top-level payment object. It bundles Pay Ins and Pay Outs together. The total across all Pay Ins must equal the total across all Pay Outs before the disbursement can be verified and sent.
Target Accounts are separate objects that hold the receiving account details for Pay Ins and Pay Outs. You create a Target Account first, then reference its ID when creating the Pay In or Pay Out.
Filtering
All GET list endpoints support filtering via a JSON body passed on the request.
| Filter Type | Description |
|---|---|
Match | Exact string match |
Contains | String contains the value |
Equal | Numeric equality |
LessThan | Numeric less than |
LessThanEqual | Numeric less than or equal |
GreaterThan | Numeric greater than |
GreaterThanEqual | Numeric greater than or equal |
Example — get all addresses in Ohio:
{
"state": [
{
"type": "Match",
"value": "OH"
}
]
}
Integration Walkthrough
Run these steps in order when onboarding a new company via the API. All examples use the test environment.
Your company is created during onboarding. Confirm it is active and the approval strategy is correct before proceeding.
GET https://testapi.direcfunds.com/Company
Authorization: bearer YOUR_TOKEN
Expected response:
{
"id": "00000000-0000-0000-0000-000000000001",
"name": "My Company LLC",
"disbursementApprovalStrategy": "Two Party Approval",
"active": true
}
Addresses are shared across branches and bank accounts. Create one before adding either.
POST https://testapi.direcfunds.com/Address
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"address1": "500 Main Street",
"address2": "Suite 200",
"city": "Cincinnati",
"state": "OH",
"postalCode": "45202"
}
Store the returned id — you will reference it when creating branches and bank accounts.
Phone numbers default to the U.S. — international phone numbers are not currently supported. Phone numbers can be attached to branches.
POST https://testapi.direcfunds.com/PhoneNumber
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"number": "5134448888"
}
A default branch is created during onboarding. Retrieve it to get its ID:
GET https://testapi.direcfunds.com/Branch
Authorization: bearer YOUR_TOKEN
Expected response:
[
{
"id": "00000000-0000-0000-0000-000000000001",
"name": "Main Branch",
"addressId": "00000000-0000-0000-0000-000000000001",
"active": true
}
]
If you need additional branches for other office locations, POST to /Branch with a name and your addressId.
A bank account serves as the source of funds for disbursements. branchPermissions is an array of branch IDs the account is available to.
POST https://testapi.direcfunds.com/BankAccount
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"bankName": "PNC",
"beneficiary": "My Company LLC",
"accountNumber": "100010004",
"routingNumber": "100200304",
"addressId": "YOUR_ADDRESS_ID",
"branchPermissions": [
"YOUR_BRANCH_ID"
],
"reverseWireAuthorized": false
}
reverseWireAuthorized to true.
For Two Party Approval, create at least two users — one with canRequest: true and a second with canApprove: true.
Add the requester (e.g. Bob — closing agent at the Satellite branch):
POST https://testapi.direcfunds.com/User
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"givenName": "Bob",
"surname": "Doe",
"email": "bob.doe@mycompany.com",
"phone": "5551234567",
"branchPermissions": [
{
"branchId": "SATELLITE_BRANCH_ID",
"canRequest": true,
"canApprove": false
}
],
"bankAccountPermissions": [
{
"branchId": "SATELLITE_BRANCH_ID",
"bankAccounts": ["BANK_ACCOUNT_ID"]
}
]
}
Add the approver (e.g. Alice — regional manager across both branches):
POST https://testapi.direcfunds.com/User
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"givenName": "Alice",
"surname": "Smith",
"email": "alice.smith@mycompany.com",
"phone": "55532328989",
"branchPermissions": [
{
"branchId": "MAIN_BRANCH_ID",
"canRequest": false,
"canApprove": true
},
{
"branchId": "SATELLITE_BRANCH_ID",
"canRequest": false,
"canApprove": true
}
],
"bankAccountPermissions": [
{
"branchId": "MAIN_BRANCH_ID",
"bankAccounts": ["BANK_ACCOUNT_ID"]
},
{
"branchId": "SATELLITE_BRANCH_ID",
"bankAccounts": ["BANK_ACCOUNT_ID"]
}
]
}
The disbursement is the high-level object. Subsequent Pay Ins and Pay Outs reference its ID.
POST https://testapi.direcfunds.com/Disbursement
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"branchId": "YOUR_BRANCH_ID",
"requesterUserId": "BOB_USER_ID",
"approverUserId": "ALICE_USER_ID"
}
Store the returned disbursement id — you will reference it on every Pay In and Pay Out.
A Target Account holds the receiving account details for a Pay In or Pay Out. Create the Pay In's target account first, then reference its returned id on the Pay In.
POST https://testapi.direcfunds.com/TargetAccount
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"bankName": "Fifth Third",
"accountNumber": "100100100",
"routingNumber": "101000000",
"accountBeneficiary": "Beneficiary",
"bankAddress1": "101 Main St",
"bankAddress2": "Suite 303",
"bankCity": "Cincinnati",
"bankState": "OH",
"bankPostalCode": "45000"
}
The Pay In references the source bank account, the Target Account you just created, and the total amount being drawn down.
POST https://testapi.direcfunds.com/PayIn
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"disbursementId": "YOUR_DISBURSEMENT_ID",
"sourceAccountReferenceId": "YOUR_BANK_ACCOUNT_ID",
"targetAccountId": "PAYIN_TARGET_ACCOUNT_ID",
"paymentRail": "Wire",
"amount": 1.00
}
paymentRail values: Wire, Ach, SamedayAch, InstantPayment.
Create a separate Target Account for the Pay Out destination. Repeat this for each Pay Out you'll add.
POST https://testapi.direcfunds.com/TargetAccount
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"bankName": "U.S. Bank",
"accountNumber": "100200200",
"routingNumber": "105000000",
"accountBeneficiary": "Beneficiary",
"bankAddress1": "500 Walnut St",
"bankCity": "Cincinnati",
"bankState": "OH",
"bankPostalCode": "45000"
}
Add one Pay Out per recipient. The amounts across all Pay Outs must sum to the Pay In total.
POST https://testapi.direcfunds.com/PayOut
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"disbursementId": "YOUR_DISBURSEMENT_ID",
"paymentRail": "Wire",
"amount": 1.00,
"transactionPurpose": "Other",
"otherPurpose": "Pest Control",
"targetAccountId": "PAYOUT_TARGET_ACCOUNT_ID",
"recipient": {
"name": "John Doe",
"dateOfBirth": "10-10-1992",
"address1": "50 Centennial Ave",
"address2": "Apt C",
"city": "Cincinnati",
"state": "OH",
"postalCode": "45000",
"phone": "513455677",
"email": "john.doe@gmail.com",
"tin": "123456789",
"recipientType": "Individual"
},
"transactionDetails": {
"memoLine": "Wire Memo"
}
}
transactionPurpose values: LoanPayoff, SellerProceeds, RealtorCommission,
FundsToClose, EarnestMoney, ClosingRelatedFees, Retainer,
TitleAgencyCommission, Other. Use otherPurpose when transactionPurpose is Other.
recipientType values: Person, Individual.
Before being sent, a disbursement and its pay outs must be verified. Under Two Party Approval, the approver triggers verification.
POST https://testapi.direcfunds.com/Disbursement/YOUR_DISBURSEMENT_ID/$Verify
Authorization: bearer YOUR_TOKEN
Content-Type: application/json
{
"approverUserId": "ALICE_USER_ID"
}
A successful response:
{
"status": "Verified",
"message": "The disbursement has been verified and is ready to send"
}
If verification fails (amount mismatch, preflight check failed, etc.), the response will describe the issue.
POST https://testapi.direcfunds.com/Disbursement/YOUR_DISBURSEMENT_ID/$Send
Authorization: bearer YOUR_TOKEN
Returns 204 No Content on success. The disbursement is now in Processing status and cannot be edited.
$VerifyAndSend:
POST https://testapi.direcfunds.com/Disbursement/YOUR_DISBURSEMENT_ID/$VerifyAndSend
{ "approverUserId": "ALICE_USER_ID" }
This only sends if verification succeeds.
Check the overall disbursement status:
GET https://testapi.direcfunds.com/Disbursement/YOUR_DISBURSEMENT_ID/Status
Authorization: bearer YOUR_TOKEN
Example response: "Processing"
Check the status of an individual Pay Out:
GET https://testapi.direcfunds.com/PayOut/YOUR_PAYOUT_ID/Status
Authorization: bearer YOUR_TOKEN
Example response: "Completed"
Disbursement Bound Actions
Three bound actions are available on a disbursement:
| Action | Path | Description |
|---|---|---|
Verify | /Disbursement/{id}/$Verify | Verifies disbursement data and pay out targets. |
Send | /Disbursement/{id}/$Send | Sends the disbursement to be processed. Requires prior verification. |
VerifyAndSend | /Disbursement/{id}/$VerifyAndSend | Combines the verify and send actions in one call. |
Response Codes
| Code | Meaning |
|---|---|
200 | OK — response body contains the requested data |
204 | No Content — used by DELETE endpoints and $Send |
400 | Bad Request — problem details returned describing the validation error |
Tips for Integration Testing
- Use the test environment (
testapi.direcfunds.com) for all development work. - Remember the order: Create a Target Account first, then reference its ID when creating the Pay In or Pay Out — this catches a lot of developers off guard.
- Test the
RequiresInconclusiveOverridepath by intentionally submitting mismatched Pay In and Pay Out amounts. - Wire cut-off times do not apply in the test environment — payments move immediately for testing purposes.
- When in doubt, use
$VerifyAndSendrather than separate$Verifyand$Sendcalls — it removes a window where the disbursement is verified but not yet sent.