Fence · Developer Documentation
Client Integration Guide
Integrate with Fence to declare assets, register supporting documents and report payment transactions, and to read back what Fence holds for your facility: assets, metric values and generated reports. This guide covers authentication, the declaration pipeline, document verification, reading data back, 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. It also lets them read data back out of Fence: the assets Fence holds (see Section 6), the facility’s metrics (see Section 7) and its reports (see Section 8).
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.
Declaration is a single call: POST /v2/declarations creates the declaration, adds the assets and triggers the pipeline together. Use the run id it returns to monitor progress.
3.2 Declaration Flow
A single endpoint that creates the portfolio declaration, adds the assets, and triggers the pipeline in one call.
Up to 50,000 assets per request. Above that the call returns 400 and nothing is created — send a larger book as several declarations.
{
"deal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"dry_run": false,
"assets": [
{
"asset_external_id": "ADV-2026-001234",
"document_external_ids": [],
"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"
}
]
}
{
"id": "f55928c9-0e3d-4f2b-9a71-6c1d5e8b04af",
"status": "queued",
"dry_run": false
}
The 202 Accepted means the declaration has been queued for asynchronous processing. Use id as the run_id in the monitoring endpoints below.
3.3 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 |
|---|---|
| validation | Schema, type and mutability checks. Carries the per-field diffs when a locked field is rejected. |
| persistence | Creates or updates the asset in Fence. |
| intake_derivation | Derives expected payments and related intake records from the declared asset. |
| borrow_base | Evaluates the asset for borrowing base inclusion, where the facility has one. |
Get a Single Asset Result
3.4 Dry Run Mode
Declarations 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 Reading Assets
Read back the assets Fence holds for your facility, using your own identifier: the asset_external_id you sent when declaring. Use these endpoints to check that Fence’s view of your book matches yours.
id and created_at) sit next to your facility’s fields as top-level keys. Which facility fields appear follows the field dictionary agreed during onboarding.6.1 List Assets
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| contract_external_id | string | No | Only the assets that belong to this contract. |
| fields | string | No | Comma-separated list of facility fields to return, for example outstanding_amount,status. id, deal_id and created_at are always returned. If omitted, every field is returned. |
| page | int | No | Page number, starting at 1. Default 1. |
| size | int | No | Items per page. Default 50. |
Your facility may support more filters on its own fields. These are agreed during onboarding. An invalid filter value returns 422.
{
"items": [
{
"id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"deal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2026-04-01T09:12:44Z",
"asset_external_id": "ADV-2026-001234",
"origination_installment_id": "INS-78901",
"organization_name": "Acme Corp",
"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"
}
],
"total": 1,
"page": 1,
"size": 50,
"pages": 1
}
The list is paginated with page and size; see Pagination. Use fields to keep large pages small.
6.2 Get an Asset
Returns one asset, with the same fields as an item of the list above. Returns 404 if no asset with this asset_external_id exists for the deal.
07 Reading Metrics
Fence calculates a set of metrics for each facility, for example outstanding principal, borrowing base, concentration limits or covenant tests, and stores every reading with the date it applies to. These are the same figures shown in the Fence dashboard. The endpoints below let your systems read them.
7.1 Overview
A metric definition describes one figure: its name, description and status. A metric value is one stored reading of that figure for a given as_of_date. The usual flow is:
- List the metric definitions for your deal and keep the
idof the ones you need. - Read the latest value of each, or its history over a date range.
- Optionally, read the explanation of how a metric is calculated.
/v3 path prefix, not /v2. Authentication is the same: send the access token from Section 2. They return values Fence has already stored. Reading them never triggers a new calculation.7.2 List Metric Definitions
Returns the metric definitions for the deals your credentials can access.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| deal_id | UUID | No | Limit the list to this deal. Repeat the parameter for several deals (?deal_id=…&deal_id=…). If omitted, metrics for every deal you can access are returned. A deal_id you cannot access returns 403. |
| name | string | No | Limit the list to metrics with this exact name. Can be repeated. |
[
{
"id": "3f2a9c1e-7b4d-4e8a-9f10-2c5d6e7f8a90",
"name": "outstanding_principal",
"status": "core",
"description": "Total outstanding principal of the eligible portfolio.",
"metadata": {}
}
]
Use the metrics whose status is core: these are the facility’s live figures, updated on Fence’s regular schedule. The list can also return metrics with other statuses. They are not meant for integrations, so ignore them.
7.3 Get the Latest Value
Returns the most recent stored reading of one metric.
{
"id": "9b8c7d6e-5f4a-4b3c-8d2e-1f0a9b8c7d6e",
"name": "outstanding_principal",
"metric_definition_id": "3f2a9c1e-7b4d-4e8a-9f10-2c5d6e7f8a90",
"deal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"value": { "value": 1250000000, "decimals": 2, "currency": "USD" },
"as_of_date": "2026-10-15T00:00:00Z",
"value_type": "amount",
"metadata": null
}
In this example the amount is 1250000000 / 10^2 = 12,500,000.00 USD. See 7.6 Value Types. Returns 404 if the metric has no stored value yet, or if the metric does not exist or belongs to a deal you cannot access.
7.4 List Value History
Returns the stored readings of one metric, filtered by date.
Query Parameters (all optional, ISO 8601 date-time):
| Parameter | Type | Required | Description |
|---|---|---|---|
| as_of_date_gte | datetime | No | Readings whose as_of_date is on or after this moment. |
| as_of_date_lte | datetime | No | Readings whose as_of_date is on or before this moment. |
| created_at_gte | datetime | No | Readings that Fence stored on or after this moment. Use it to fetch only what is new since your last sync. |
| created_at_lte | datetime | No | Readings that Fence stored on or before this moment. |
The response is an array of the same objects as in 7.3. Without any filter it returns the metric’s full history.
as_of_date is the date the figure describes. created_at is when Fence stored it. Fence can store a corrected reading for a past date, so to stay in sync, poll with created_at_gte set to the time of your previous poll.You can also fetch a single reading by its id: GET /v3/metrics/{metric_definition_id}/values/{metric_value_id}.
7.5 Get a Metric Explanation
Returns how a metric is calculated: where its data comes from, the formula, and the meaning of each term.
{
"metric_definition_id": "3f2a9c1e-7b4d-4e8a-9f10-2c5d6e7f8a90",
"description": "Total outstanding principal of the eligible portfolio.",
"data_source": "Declared assets",
"formula_latex": "\\sum_{i} P_i",
"sub_formula_latex": null,
"terms": [
{
"key": "P_i",
"label": "Outstanding principal",
"description": "Outstanding principal of eligible asset i.",
"metric_definition_id": null
}
],
"steps": [
"Select the eligible assets.",
"Add up their outstanding principal."
]
}
Formulas are written in LaTeX. When a term is itself another metric, its metric_definition_id is set so you can follow it. Not every metric has an explanation yet. Those return 200 with a null body.
7.6 Value Types
The shape of value depends on value_type. Compare value_type without regard to case.
| value_type | Shape of value |
|---|---|
| amount | Object {"value": int, "decimals": int, "currency": string}. The amount is value / 10^decimals. Integers avoid rounding errors. |
| number | A number, for example a count of assets. |
| percentage | A number. Check the metric’s description for whether it is a ratio (0.05) or a percentage (5). |
| boolean | true or false, for example whether a covenant test passes. |
| datetime | An ISO 8601 date-time string. |
| *_per_entity | amount_per_entity, number_per_entity, percentage_per_entity, datetime_per_entity: an object whose keys are entities (for example a country, an originator or a tranche) and whose values have the shape of the base type above. |
| time_series | A series of dated points. The exact shape depends on the metric and is agreed during onboarding. |
value can be null when the metric ran but had nothing to measure on that date, for example when no assets were in scope.
08 Downloading Reports
Fence generates reports for each facility, for example borrowing base certificates, portfolio tapes or covenant reports. Which reports exist depends on the facility and is agreed during onboarding. Downloading one takes two calls: list the reports available for your deal, then request a download link for the one you want.
8.1 List Available Reports
[
{
"report_name": "borrowing_base_certificate",
"report_description": "Borrowing base certificate as of a given date.",
"report_type": "excel",
"report_params": [
{
"name": "as_of_date",
"description": "Date the report is calculated for.",
"input_type": "date",
"options": null
}
]
}
]
Each report lists the parameters it accepts in report_params. input_type is one of text, date, number or boolean. When options is set, the value must be one of those options. report_type is the file format: pdf, excel (.xlsx), csv or txt.
8.2 Download a Report
Generates the report and returns a temporary link to the file.
{
"report_name": "borrowing_base_certificate",
"params": [
{ "name": "as_of_date", "value": "2026-09-30" }
]
}
| Field | Type | Required | Description |
|---|---|---|---|
| report_name | string | Yes | A report_name from 8.1. |
| params | list of objects | No | One {"name", "value"} pair per parameter. value is always a string, whatever the input_type: send dates as "YYYY-MM-DD", numbers as "100" and booleans as "true" or "false". |
{
"url": "https://s3.amazonaws.com/...",
"expiration": 3600
}
Send a GET request to url to download the file. The link is valid for expiration seconds (one hour). After that, call this endpoint again for a new link.
Errors:
| Code | Meaning |
|---|---|
| 400 | A parameter is missing or has an invalid value. The message says which. |
| 403 | You cannot access this deal, or this report is not available to your account. |
| 404 | No report with this report_name exists for this deal. |
09 Schemas Reference
Canonical request and response shapes for the declaration, read-back, metrics and reports endpoints. Responses can include fields not listed here. Ignore any field you do not recognise.
Asset Declaration — Request
Portfolio Declaration Response
Declaration Run Response
Per-Asset Result Response
Asset (read)
Paginated List
Metric Definition Response
Metric Value Response
Metric Explanation Response
Available Report
Report Download — Request
Report Download — Response
Supported Currencies
54 ISO currency codes are accepted in the currency field.
10 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). |
| 409 | Conflict. Returned when a facility configured for streaming is declared through an endpoint that does not support it; the message names the endpoint to use. |
| 500 | Internal server error. |
Pagination
The declaration endpoints in section 3 paginate as follows. The asset list in section 6 takes size (default 50) instead and ignore page_size. The metrics and reports endpoints in sections 7 and 8 are not paginated: they return a plain array.
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | int | 1 | Page number (1-indexed). |
| page_size | int | 100 | Items per page, maximum 1000; above that the call returns 422. Note the asymmetry: the request parameter is page_size, while the response reports it back as size. |
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
completed,completed_with_errorsorfailed, 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.