Fence · Developer Documentation
Client Integration Guide
Integrate with Fence to declare assets, register supporting documents, and report payment transactions. This guide covers authentication, the declaration pipeline, document verification, and error handling for the v2 API.
01 Getting Started
Fence is a tech-native facility agency for asset-backed finance. The API lets your systems declare portfolio assets, attach verification documents, and record payment transactions against a credit facility in real time.
1.1 Environments
Fence provides two environments. All API paths in this guide are relative to the environment base URL.
| Environment | Base URL | Purpose |
|---|---|---|
| Sandbox | https://dev-api.fence.finance | Integration testing. Non-production data; safe to experiment. |
| Production | https://api.fence.finance | Live environment. |
For example, /v2/{deal_id}/documents resolves to https://dev-api.fence.finance/v2/{deal_id}/documents in Sandbox. Use the environment toggle in the top bar to switch every example to your target environment.
1.2 Facility Scope
Each facility is identified in Fence by a deal_id — a UUID that scopes requests to that facility. Most endpoints include {deal_id} as a path parameter. Fence will provide separate deal_id values for the Sandbox and Production environments during onboarding.
POST /v1/login/access-token) is the exception: it does not take a deal_id. Authentication is performed with client_id + client_secret, and the returned token is implicitly scoped to the facilities your account is linked to.1.3 Credentials
API credentials (client_id + client_secret) and your deal_id are provisioned by Fence per environment. To request them, contact your Fence onboarding representative. Fence will provide the credentials in a secure way during onboarding.
02 Authentication
Fence uses OAuth 2.0. Machine-to-machine integrations should use the Client Credentials grant.
Obtaining an Access Token
deal_id. Authentication is performed with client_id + client_secret, and the returned token is implicitly scoped to the deals your account is linked to.Client Credentials Grant (recommended for machine-to-machine integrations):
POST /v1/login/access-token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id={your_client_id}
&client_secret={your_client_secret}
{
"access_token": "eyJraWQiOi...",
"token_type": "Bearer",
"expires_in": 3600
}
Using the Token
Include the token in every subsequent request:
Authorization: Bearer {access_token}
Authorization Model
Clients are authorized at the deal level. Your credentials grant access only to the deals your account is linked to.
- Actions:
read,write,create,declare— each endpoint requires a specific action. - Attempting to access a deal you're not authorized for returns 403 Forbidden.
03 Asset Declaration
3.1 Overview
Asset declaration is the process of submitting your portfolio of assets (e.g., loans, receivables, advances) to Fence. Fence validates, persists, and processes these assets through a configurable pipeline that can include eligibility checks, borrow base calculations, and expected payment generation.
Two approaches are available. Both trigger the same processing pipeline and produce the same results.
Approach A
Step-by-step
Best for large portfolios and incremental asset addition. Fine-grained control over the declaration lifecycle.
/v2/portfolio_declarations/Approach B
Fast-track
Best for simpler integrations and single-call declaration. Create, add assets, and trigger in one request.
/v2/declarations/3.2 Step-by-Step Declaration Flow
This approach gives you fine-grained control over the declaration lifecycle.
Step 1 · Create a Portfolio Declaration
{
"name": "Daily Portfolio Snapshot 2026-04-07",
"deal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
{
"id": "d4e5f6a7-b8c9-0123-4567-890abcdef012",
"deal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Daily Portfolio Snapshot 2026-04-07",
"asset_count": null,
"created_at": "2026-04-07T10:00:00Z",
"updated_at": "2026-04-07T10:00:00Z"
}
Step 2 · Add Assets to the Declaration
You can call this endpoint multiple times to add assets in batches.
[
{
"external_id": "ADV-2026-001234",
"contract_external_id": null,
"properties": {
"origination_installment_id": "INS-78901",
"organization_name": "Acme Corp",
"employer_name": "Acme Corp",
"employee_id": "EMP-456",
"user_state": "CA",
"user_country": "US",
"earned_wage": 2500.00,
"total_principal_amount": 800.00,
"total_fee_amount": 12.00,
"outstanding_principal_amount": 800.00,
"outstanding_fee_amount": 12.00,
"outstanding_amount": 812.00,
"transaction_date": "2026-04-01",
"due_date": "2026-04-15",
"currency": "USD",
"status": "active"
}
}
]
Key points:
external_id— Your unique identifier for the asset. Used for idempotency (upsert on re-declaration).contract_external_id— Optional. Links the asset to a specific contract. Ifnull, Fence may auto-create a default umbrella contract (deal-dependent).properties— Deal-specific fields. The exact schema is agreed upon during onboarding and validated by Fence during the declaration pipeline.
Step 3 · Trigger the Declaration
{
"dry_run": false
}
{
"id": "b1c2d3e4-f5a6-7890-bcde-f01234567890",
"status": "QUEUED",
"dry_run": false
}
The 202 Accepted status indicates the declaration has been queued for asynchronous processing. Use the id to monitor progress.
3.3 Fast-Track Declaration Flow
A single endpoint that creates a portfolio declaration, adds assets, and triggers the pipeline in one call.
{
"deal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"dry_run": false,
"assets": [
{
"external_id": "ADV-2026-001234",
"properties": {
"origination_installment_id": "INS-78901",
"organization_name": "Acme Corp",
"employee_id": "EMP-456",
"user_state": "CA",
"user_country": "US",
"total_principal_amount": 800.00,
"total_fee_amount": 12.00,
"outstanding_amount": 812.00,
"transaction_date": "2026-04-01",
"due_date": "2026-04-15",
"currency": "USD",
"status": "active"
}
}
]
}
3.4 Monitoring Declaration Progress
List Declaration Runs for a Deal
Declaration Run Statuses:
| Status | Description |
|---|---|
| QUEUED | Declaration received, waiting for processing. |
| PROCESSING | Pipeline is actively processing assets. |
| COMPLETED | All pipeline steps finished successfully. All assets are declarable. |
| COMPLETED_WITH_ERRORS | Pipeline finished but some assets are not declarable (e.g., failed eligibility). Check per-asset results. |
| FAILED | Pipeline encountered a critical error. Check error_details. |
Get a Specific Declaration Run
List Per-Asset Results
Per-Asset Result Statuses:
| Status | Meaning |
|---|---|
| SUCCESSFUL | Asset passed this pipeline step. |
| UNSUCCESSFUL | Asset was processed but did not meet the criteria (e.g., failed eligibility). Still created but flagged. |
| FAILED | An error occurred processing this asset at this step. |
Pipeline Steps:
| Step Name | Description |
|---|---|
| ASSET_VERIFICATION | Validates asset against deal-specific eligibility criteria. |
| BORROW_BASE_VERIFICATION | Evaluates asset for borrow base inclusion (if applicable). |
| ASSET_CREATION | Creates or updates the asset in Fence. details.operation is created or updated. |
Get a Single Asset Result
3.5 Dry Run Mode
Both declaration flows support dry_run: true. In dry-run mode:
- The full validation pipeline runs (schema validation, eligibility checks, borrow base).
- No data is persisted — assets are not created/updated, no expected payments are generated.
- Results are still available via the monitoring endpoints.
Use this to validate your data before committing.
Recommended workflow:
- Submit a declaration with
dry_run: true. - Inspect per-asset results for validation issues.
- Fix any issues in your source data.
- Submit again with
dry_run: false.
04 Document Declaration
For deals that require verification, Fence validates each receivable against supporting portfolio documents (e.g., loan agreements, repayment authorizations, invoices). Clients register documents in batch, upload the files to S3 using the returned presigned URLs, and Fence processes them asynchronously.
document_external_id is the join key Fence uses to reconcile each document with its declared asset at verification time. The value convention (e.g., matching a specific asset field) is agreed during onboarding.4.1 Document Registration
Register one or more documents in a single call. Fence creates the document records and returns presigned S3 URLs the client PUTs the file bytes to.
{
"documents": [
{
"document_category": "loan_agreement",
"document_external_id": "DOC-2026-001234",
"file_name_with_ext": "agreement.pdf",
"title": "Loan Agreement - Acme Borrower"
}
]
}
Request Field Reference:
| Field | Type | Required | Description |
|---|---|---|---|
| document_category | string | Yes | Category of the document (e.g., loan_agreement, invoice, repayment_authorization). Agreed during onboarding. |
| document_external_id | string | Yes | Client-provided unique identifier. Used as the join key to reconcile this document with its declared asset during verification; the specific value convention is agreed during onboarding. |
| file_name_with_ext | string | Yes | Filename including extension (e.g., agreement.pdf). |
| title | string | No | Human-readable title. Defaults to filename if omitted. |
{
"documents": [
{
"document_external_id": "DOC-2026-001234",
"presigned_url": "https://s3.amazonaws.com/...",
"expires_at": "2026-04-07T11:00:00Z",
"uploaded_at": null
}
]
}
For each document in the response, PUT the file bytes to the returned presigned_url before expires_at. Presigned URLs are valid for 1 hour from issuance.
document_external_id returns 422 Unprocessable Entity. To retry an upload whose URL has expired or whose PUT failed, use Section 4.2 Upload Status to request a fresh presigned URL — do not re-register the document.4.2 Upload Status
Use this endpoint for two purposes: (1) confirming Fence has received each file from S3, and (2) retrieving a fresh presigned URL for any document that has not yet been uploaded.
uploaded_at is populated once Fence registers the S3 event. Until then, each call to this endpoint returns a newly-signed presigned_url with a fresh expires_at one hour in the future, so you can safely retry uploads that failed or whose URL has expired without re-registering the document.
{
"documents": [
{
"document_external_id": "DOC-2026-001234",
"presigned_url": null,
"expires_at": null,
"uploaded_at": "2026-04-07T10:15:32Z"
},
{
"document_external_id": "DOC-2026-001235",
"presigned_url": "https://s3.amazonaws.com/...",
"expires_at": "2026-04-07T11:00:00Z",
"uploaded_at": null
}
]
}
Two mutually exclusive states are returned per document:
| State | Fields populated | Meaning |
|---|---|---|
| Pending | presigned_url · expires_at; uploaded_at is null |
Document registered but not yet uploaded. The URL is freshly-signed on every call — retry as needed. |
| Uploaded | uploaded_at; presigned_url and expires_at are null |
File has been received by Fence. No new URL will ever be issued for this document — the record is now immutable for verification. |
Unrecognized document_external_id values are silently omitted from the response (no error). The endpoint is safe to call repeatedly; it has no side effects on the document record.
05 Payment Transaction Declaration
Payment transactions represent financial movements associated with a deal — intakes (incoming payments), expenses, repayments, outbound transfers, dispositions, and more.
Base URL — all payment transaction endpoints are scoped to a deal:
5.1 Bulk Create
Create multiple payment transactions in a single request. The processing mode is controlled by the dry_run query parameter:
{
"data": [
{
"external_id": "TXN-20260407-001",
"amount": 812.00,
"transaction_date": "2026-04-07T14:30:00Z",
"currency": "USD",
"transaction_type": "intake",
"from_": "Borrower Account",
"to_": "SPV Collection Account",
"memo": "Repayment for advance ADV-2026-001234",
"bank_account_id": "e5f6a7b8-c9d0-1234-ef56-7890abcdef01",
"metadata": {
"metadata_type": "intake",
"type": "repayment_collection"
}
}
]
}
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| dry_run | bool | true | If true, validates without persisting (synchronous 200). If false, validates and enqueues for async processing (202). Set to false explicitly to create transactions. |
Transaction Types
| Type | Description | Metadata Type |
|---|---|---|
| intake | Incoming payment to the deal (collections, deposits) | intake |
| intake_meta | Metadata-only intake (no financial movement) | intake_meta |
| expense | Fee or expense charged to the deal | expense |
| repayment | Repayment from borrower to lender | repayment |
| outbound | Outgoing payment from the deal | outgoing_type |
| disposition | Asset disposition event | — |
| borrow | Borrowing/draw event | borrow |
| utilization | Facility utilization event | utilization |
| waterfall_payment | Waterfall distribution payment | — |
| outgoing_type | General outgoing payment | outgoing_type |
Transaction Fields
| Field | Type | Required | Description |
|---|---|---|---|
| external_id | string | Yes | Your unique identifier for this transaction. |
| amount | float | Yes | Transaction amount. |
| transaction_date | datetime | Yes | When the transaction occurred. |
| currency | string | Yes | ISO currency code (e.g., "USD"). |
| transaction_type | string | No | One of the transaction types above. |
| from_ | string | No | Source account identifier. |
| to_ | string | No | Destination account identifier. |
| memo | string | No | Description or notes. |
| bank_account_id | UUID | No | Associated bank account in Fence. |
| iban | string | No | Recipient IBAN. |
| routing_number | string | No | Recipient routing number (when no IBAN). |
| account_number | string | No | Recipient account number (when no IBAN). |
| metadata | object | No | Type-specific metadata (see below). |
Metadata by Transaction Type
{ "metadata_type": "intake", "type": "repayment_collection" }
{ "metadata_type": "repayment", "repayment_type": "scheduled" }
{
"metadata_type": "expense",
"category": "servicing",
"description": "Monthly servicing fee"
}
{
"metadata_type": "borrow",
"borrow_type": "initial_draw",
"tranche_name": "Tranche A"
}
{
"metadata_type": "utilization",
"utilization_type": "draw",
"tranche_name": "Tranche A"
}
{
"metadata_type": "outgoing_type",
"outgoing_type": "distribution",
"payment_date": "2026-04-07T00:00:00Z"
}
deal_specific_metadata dict for custom fields.06 Schemas Reference
Canonical request and response shapes for the declaration endpoints.
Asset Declaration — Request
Asset Declaration — Fast Track Request
Portfolio Declaration Response
Declaration Run Response
Per-Asset Result Response
Supported Currencies
54 ISO currency codes are accepted in the currency field.
07 Error Handling
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success (synchronous operations). |
| 201 | Resource created successfully. |
| 202 | Accepted — async processing started. |
| 400 | Bad request — validation error in request body. |
| 401 | Unauthorized — invalid or expired token. |
| 403 | Forbidden — you don't have access to this deal/resource. |
| 404 | Resource not found. |
| 422 | Unprocessable Entity — validation error (e.g., duplicate document_external_id, missing file extension). |
| 500 | Internal server error. |
Pagination
All list endpoints support pagination:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | int | 1 | Page number (1-indexed). |
| size | int | 20 | Items per page. |
Response includes: total, page, size, pages.
Idempotency
- Assets:
external_idis the idempotency key. Re-declaring an asset with the sameexternal_idperforms an upsert (updates mutable fields, preserves immutable ones). - Payment Transactions:
external_idshould be unique per transaction. - Documents:
document_external_idis unique per deal. Re-registering returns 422; use Section 4.2 Upload Status to retrieve a fresh presigned URL for any pending document.
Recommended Polling Strategy
For asset declarations and bulk payment batches:
- Poll
GET /v2/declarations/{run_id}every 10–30 seconds. - Check the
statusfield. - Once
COMPLETEDorFAILED, inspect per-asset results if needed. - Typical processing time depends on portfolio size (seconds for small batches, minutes for large portfolios).
For document uploads, poll GET /v2/{deal_id}/documents (see Section 4.2) every 5–10 seconds until uploaded_at is populated. Each poll on a pending document returns a fresh presigned URL, so if a PUT fails you can retry immediately with the URL from the next poll.