Developers · API v1
Tenara API developer guide
Connect your loan origination system, core banking platform or app to Tenara. Create borrowers and applications, then get an explainable risk assessment for each one.
Quickstart
All endpoints live under /api/v1 and take and return JSON. You need an API key to begin.
- Ask a user with the Developer or Organization Admin role to create an API key in the Tenara dashboard. Start with a sandbox key (
tnr_test_…). - Send the key as a bearer token on every request.
- Build and test against the sandbox. When you go live, swap in a production key (
tnr_live_…); nothing else changes.
$ curl "$TENARA_API_URL/api/v1/borrowers" \
-H "Authorization: Bearer $TENARA_API_KEY"The full OpenAPI document is served at /api/v1/openapi.json, with interactive references at /docs and /redoc on your Tenara API host.
Authentication
Every request carries an API key in the Authorization header. The key's prefix fixes its environment, so you never send an environment header.
| Key format | Environment | Use for |
|---|---|---|
tnr_test_<8 hex>_<secret> | Sandbox | Development and testing |
tnr_live_<8 hex>_<secret> | Production | Live lending data |
- Shown once. Tenara stores only a hash of each key. If you lose one, revoke it and create a new one.
- Integration scope. Keys can read and create borrowers, applications and assessments. Dashboard endpoints (users, API keys, audit logs, organization, credit decisions) return
403. - Separated data. Sandbox and production never mix. An ID from the other environment, or from another institution, returns
404. - Suspension. If your institution is suspended, all of its keys stop working at once with
401.
Integration flow
A typical integration makes three calls per loan request, then reads the result.
Step 1
Create the borrower
POST /borrowers, once per customer, keyed by your own reference.
Step 2
Create the application
POST /applications with the amount, currency and term.
Step 3
Run an assessment
POST /assessments with the borrower's financial profile.
Step 4
Use the result
Read the score, band, affordability and risk factors from the response.
The lending decision stays with your institution. Credit officers record approvals and rejections in the Tenara dashboard; API keys cannot decide on applications.
Endpoints
These are the endpoints an API key can call. Paths are relative to /api/v1.
| Method | Path | What it does |
|---|---|---|
| GET | /borrowers | List and search borrowers |
| POST | /borrowers | Create a borrower |
| GET | /borrowers/{borrower_id} | Get a borrower, with their latest assessment |
| GET | /borrowers/{borrower_id}/applications | A borrower's credit applications |
| GET | /borrowers/{borrower_id}/assessments | A borrower's assessment history |
| GET | /applications | List credit applications |
| POST | /applications | Create a credit application |
| GET | /applications/{application_id} | Get a credit application |
| GET | /assessments | List assessments |
| POST | /assessments | Run a credit assessment |
| GET | /assessments/{assessment_id} | Get an assessment |
List endpoints are paginated; see requests and responses.
Borrowers
A borrower is a person or business you lend to. external_reference is your own ID for them and must be unique per environment, so retrying a create returns 409 instead of a duplicate.
{
"external_reference": "CUST-10442",
"borrower_type": "INDIVIDUAL",
"full_name": "Amina Wanjiru",
"country_code": "KE",
"employment_status": "SALARIED"
}| Field | Notes |
|---|---|
external_reference | Required. Your ID for the borrower. |
full_name | Required. Up to 200 characters. |
country_code | Required. ISO 3166-1 alpha-2, e.g. KE. |
borrower_type | INDIVIDUAL (default) or BUSINESS. |
employment_status | SALARIED, SELF_EMPLOYED or INFORMAL. |
email, phone, date_of_birth, city, employer_name | Optional. date_of_birth must be in the past. |
Applications
An application is one loan request from a borrower. New applications start as SUBMITTED.
{
"borrower_id": "<borrower id>",
"amount": "150000.00",
"currency": "KES",
"term_months": 12,
"purpose": "Working capital"
}| Field | Notes |
|---|---|
borrower_id | Required. A borrower in the same environment. |
amount | Required. A decimal string greater than zero. |
term_months | Required. 1 to 360. |
currency | ISO 4217 code. Defaults to your institution's currency. |
purpose, external_reference | Optional. |
An application's status moves through SUBMITTED, UNDER_REVIEW, APPROVED, REJECTED or WITHDRAWN as your team decides on it in the dashboard.
Assessments
An assessment scores a borrower for a loan. Send exactly one of application_id (an existing application) or loan (a standalone request with amount, term_months and an optional currency).
{
"borrower_id": "<borrower id>",
"application_id": "<application id>",
"financials": {
"monthly_income": "85000.00",
"monthly_expenses": "40000.00",
"existing_monthly_debt": "12000.00",
"employment_type": "SALARIED",
"employment_months": 30,
"recent_credit_inquiries": 1,
"has_prior_default": false
}
}Financial profile
| Field | Notes |
|---|---|
monthly_income, monthly_expenses | Decimal strings. |
existing_monthly_debt | Current monthly repayments on existing credit. |
employment_type | SALARIED, SELF_EMPLOYED, INFORMAL or UNEMPLOYED. |
employment_months | Months at the current income source, 0 to 720. |
recent_credit_inquiries | Number of recent credit inquiries. |
has_prior_default | true or false. |
The result
The response (201) is the finished assessment. Assessments never change; running again creates a new one, so the history is kept.
| Field | Meaning |
|---|---|
risk_score | 300–850. Higher means lower risk. |
risk_band | LOW (≥720), MEDIUM (650–719), HIGH (580–649), VERY_HIGH (<580) |
probability_of_default | Estimated probability of default, 0–1 |
affordability_ratio | Total monthly debt service including the new loan, divided by income |
recommended_loan_amount | The most the affordability model supports, never above the request |
risk_factors | What drove the score, most influential first |
model | Name and version of the engine that produced the result |
inputs | The exact inputs the engine used |
mock-scorecard@mock-v1, is a deterministic placeholder for integration work. It is not a validated credit model and must not be used for real lending decisions.Requests and responses
- Send
Content-Type: application/json. Field names aresnake_case. - Money is a decimal string (
"150000.00"), never a float. Currencies are ISO 4217 codes. - Dates are
YYYY-MM-DD; timestamps are ISO 8601 in UTC. - Unknown request fields are ignored. Ignore unknown response fields too; new ones may appear.
- Send
X-Request-ID(8–128 characters,[A-Za-z0-9._-]) to match requests to your logs. Every response carries one.
Single resources come back as plain objects. Lists use one envelope, paged with ?page= (from 1) and ?page_size= (up to 100).
{
"data": [
"…"
],
"pagination": {
"page": 1,
"page_size": 25,
"total": 132
}
}Errors
Every error uses the same shape. Validation errors list field locations, never the submitted values.
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"request_id": "req_4c1e…",
"details": [
{
"loc": [
"body",
"amount"
],
"msg": "…",
"type": "…"
}
]
}
}| HTTP | code | Meaning |
|---|---|---|
| 400 | bad_request | Malformed request |
| 401 | unauthenticated | Missing, invalid, expired or revoked key |
| 403 | forbidden | The key cannot call this endpoint |
| 404 | not_found | Does not exist, or belongs to another institution or environment |
| 409 | conflict | Duplicate external_reference, or an invalid state change |
| 422 | validation_error | Input failed validation; see details |
| 429 | rate_limited | Too many requests; wait for Retry-After |
| 500 | internal_error | Unexpected error; quote the request_id to support |
| 503 | service_unavailable | A dependency is unavailable |
Rate limits and retries
Each API key may make 120 requests a minute by default. Past that you get 429 with a Retry-After header; wait that many seconds before retrying.
Creating a borrower is safe to retry because external_reference is unique. A general Idempotency-Key header for every POST is planned.
Versioning
v1 only gains additive changes: new endpoints, new optional request fields and new response fields. Removing or renaming a field, or changing its meaning, ships as /api/v2 alongside v1.
Ready for sandbox access?
Request access and we'll set up your institution.