Fence API Integration Guide API v2

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.

AudienceTechnical integration teams API Versionv2 Active Base URLhttps://dev-api.fence.finance

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.

EnvironmentBase URLPurpose
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.

The login endpoint (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

The login endpoint does not require a 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 https://dev-api.fence.finance/v1/login/access-token
HTTP Request
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}
Response200 OK
{
  "access_token": "eyJraWQiOi...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Using the Token

Include the token in every subsequent request:

Header
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

POST https://dev-api.fence.finance/v2/portfolio_declarations/
Request Bodyapplication/json
{
  "name": "Daily Portfolio Snapshot 2026-04-07",
  "deal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Response201 Created
{
  "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.

POST https://dev-api.fence.finance/v2/portfolio_declarations/{portfolio_declaration_id}/assets
Request Body — list of assets
[
  {
    "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. If null, 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

POST https://dev-api.fence.finance/v2/portfolio_declarations/{portfolio_declaration_id}/declare
Request Body
{
  "dry_run": false
}
Response202 Accepted
{
  "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.

POST https://dev-api.fence.finance/v2/declarations/
Request Body
{
  "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"
      }
    }
  ]
}
Batching recommendation. The declaration queue processes declarations for a given deal sequentially. Each declaration has fixed infrastructure overhead, so sending many single-asset declarations will create significant backlog. Instead, batch assets over a period (e.g., every 15 minutes, hourly, or daily depending on your reporting needs) and send them in a single declaration.

3.4 Monitoring Declaration Progress

List Declaration Runs for a Deal

GET https://dev-api.fence.finance/v2/declarations/?deal_id={deal_id}&page=1&size=20

Declaration Run Statuses:

StatusDescription
QUEUEDDeclaration received, waiting for processing.
PROCESSINGPipeline is actively processing assets.
COMPLETEDAll pipeline steps finished successfully. All assets are declarable.
COMPLETED_WITH_ERRORSPipeline finished but some assets are not declarable (e.g., failed eligibility). Check per-asset results.
FAILEDPipeline encountered a critical error. Check error_details.

Get a Specific Declaration Run

GET https://dev-api.fence.finance/v2/declarations/{run_id}

List Per-Asset Results

GET https://dev-api.fence.finance/v2/declarations/{run_id}/assets?page=1&size=50

Per-Asset Result Statuses:

StatusMeaning
SUCCESSFULAsset passed this pipeline step.
UNSUCCESSFULAsset was processed but did not meet the criteria (e.g., failed eligibility). Still created but flagged.
FAILEDAn error occurred processing this asset at this step.

Pipeline Steps:

Step NameDescription
ASSET_VERIFICATIONValidates asset against deal-specific eligibility criteria.
BORROW_BASE_VERIFICATIONEvaluates asset for borrow base inclusion (if applicable).
ASSET_CREATIONCreates or updates the asset in Fence. details.operation is created or updated.

Get a Single Asset Result

GET https://dev-api.fence.finance/v2/declarations/{run_id}/assets/{asset_id}

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:

  1. Submit a declaration with dry_run: true.
  2. Inspect per-asset results for validation issues.
  3. Fix any issues in your source data.
  4. 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.

POST https://dev-api.fence.finance/v2/{deal_id}/documents
Request Body
{
  "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:

FieldTypeRequiredDescription
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.
Response201 Created
{
  "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.

PUT {presigned_url}
Re-registering with an existing 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.

GET https://dev-api.fence.finance/v2/{deal_id}/documents?document_external_ids=DOC-2026-001234,DOC-2026-001235
Response200 OK
{
  "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:

StateFields populatedMeaning
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:

https://dev-api.fence.finance/v2/{deal_id}/payments

5.1 Bulk Create

Create multiple payment transactions in a single request. The processing mode is controlled by the dry_run query parameter:

POST https://dev-api.fence.finance/v2/{deal_id}/payments/bulk?dry_run=false
Request Body
{
  "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:

ParameterTypeDefaultDescription
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

TypeDescriptionMetadata Type
intakeIncoming payment to the deal (collections, deposits)intake
intake_metaMetadata-only intake (no financial movement)intake_meta
expenseFee or expense charged to the dealexpense
repaymentRepayment from borrower to lenderrepayment
outboundOutgoing payment from the dealoutgoing_type
dispositionAsset disposition event
borrowBorrowing/draw eventborrow
utilizationFacility utilization eventutilization
waterfall_paymentWaterfall distribution payment
outgoing_typeGeneral outgoing paymentoutgoing_type

Transaction Fields

FieldTypeRequiredDescription
external_idstringYesYour unique identifier for this transaction.
amountfloatYesTransaction amount.
transaction_datedatetimeYesWhen the transaction occurred.
currencystringYesISO currency code (e.g., "USD").
transaction_typestringNoOne of the transaction types above.
from_stringNoSource account identifier.
to_stringNoDestination account identifier.
memostringNoDescription or notes.
bank_account_idUUIDNoAssociated bank account in Fence.
ibanstringNoRecipient IBAN.
routing_numberstringNoRecipient routing number (when no IBAN).
account_numberstringNoRecipient account number (when no IBAN).
metadataobjectNoType-specific metadata (see below).

Metadata by Transaction Type

Intake
{ "metadata_type": "intake", "type": "repayment_collection" }
Repayment
{ "metadata_type": "repayment", "repayment_type": "scheduled" }
Expense
{
  "metadata_type": "expense",
  "category": "servicing",
  "description": "Monthly servicing fee"
}
Borrow
{
  "metadata_type": "borrow",
  "borrow_type": "initial_draw",
  "tranche_name": "Tranche A"
}
Utilization
{
  "metadata_type": "utilization",
  "utilization_type": "draw",
  "tranche_name": "Tranche A"
}
Outgoing Type
{
  "metadata_type": "outgoing_type",
  "outgoing_type": "distribution",
  "payment_date": "2026-04-07T00:00:00Z"
}
All metadata types accept an optional deal_specific_metadata dict for custom fields.

06 Schemas Reference

Canonical request and response shapes for the declaration endpoints.

Asset Declaration — Request

CreatePortfolioDeclarationAssetItem
external_idstringRequired, min 1 char
contract_external_idstring | null
propertiesobject | nullDeal-specific fields

Asset Declaration — Fast Track Request

FastTrackDeclarationApiInput
deal_idUUIDRequired
dry_runbooleanDefault: false
assetslist of objectsRequired, min 1 item. Each object must contain at minimum: external_id (string) and properties (object, deal-specific).

Portfolio Declaration Response

PortfolioDeclarationApiResponse
idUUID
deal_idUUID
namestring | null
asset_countinteger | null
created_atdatetime
updated_atdatetime

Declaration Run Response

DeclarationRunDetailApiResponse
idUUID
portfolio_declaration_idUUID
dry_runboolean
status"QUEUED" | "PROCESSING" | "COMPLETED" | "COMPLETED_WITH_ERRORS" | "FAILED"
summaryobject | null
error_detailsobject | null
started_atdatetime | null
completed_atdatetime | null
created_atdatetime | null
updated_atdatetime | null

Per-Asset Result Response

DeclarationRunAssetResultApiResponse
asset_idUUID
external_asset_idstring
contract_external_idstring | null
propertiesobject | null
is_eligibleboolean | null
is_declarableboolean | null
resultslist of AssetResultApiResponseEach result: id (UUID), step_name (string), status ("SUCCESSFUL" | "UNSUCCESSFUL" | "FAILED"), details (object | null).

Supported Currencies

54 ISO currency codes are accepted in the currency field.

ALLARSAUDBGNBOBBRLBZDCADCHFCLPCNYCOPCRCCZKDKKEGPEURGBPGELGTQHKDHUFIDRILSINRISKJPYKHRKRWLAKLKRMADMKDMVRMXNMYRNOKNZDPABPENPHPPLNPYGRONRSDSEKSGDTHBTRYTZSUSDUYUVNDZAR

07 Error Handling

HTTP Status Codes

CodeMeaning
200Success (synchronous operations).
201Resource created successfully.
202Accepted — async processing started.
400Bad request — validation error in request body.
401Unauthorized — invalid or expired token.
403Forbidden — you don't have access to this deal/resource.
404Resource not found.
422Unprocessable Entity — validation error (e.g., duplicate document_external_id, missing file extension).
500Internal server error.

Pagination

All list endpoints support pagination:

ParameterTypeDefaultDescription
pageint1Page number (1-indexed).
sizeint20Items per page.

Response includes: total, page, size, pages.

Idempotency

  • Assets: external_id is the idempotency key. Re-declaring an asset with the same external_id performs an upsert (updates mutable fields, preserves immutable ones).
  • Payment Transactions: external_id should be unique per transaction.
  • Documents: document_external_id is 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:

  1. Poll GET /v2/declarations/{run_id} every 10–30 seconds.
  2. Check the status field.
  3. Once COMPLETED or FAILED, inspect per-asset results if needed.
  4. 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.