Skip to content
tenara

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.

  1. 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_…).
  2. Send the key as a bearer token on every request.
  3. Build and test against the sandbox. When you go live, swap in a production key (tnr_live_…); nothing else changes.
Terminal
bash
$ 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 formatEnvironmentUse for
tnr_test_<8 hex>_<secret>SandboxDevelopment and testing
tnr_live_<8 hex>_<secret>ProductionLive 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.

  1. Step 1

    Create the borrower

    POST /borrowers, once per customer, keyed by your own reference.

  2. Step 2

    Create the application

    POST /applications with the amount, currency and term.

  3. Step 3

    Run an assessment

    POST /assessments with the borrower's financial profile.

  4. 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.

MethodPathWhat it does
GET/borrowersList and search borrowers
POST/borrowersCreate a borrower
GET/borrowers/{borrower_id}Get a borrower, with their latest assessment
GET/borrowers/{borrower_id}/applicationsA borrower's credit applications
GET/borrowers/{borrower_id}/assessmentsA borrower's assessment history
GET/applicationsList credit applications
POST/applicationsCreate a credit application
GET/applications/{application_id}Get a credit application
GET/assessmentsList assessments
POST/assessmentsRun 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.

POST /api/v1/borrowers
application/json
{
  "external_reference": "CUST-10442",
  "borrower_type": "INDIVIDUAL",
  "full_name": "Amina Wanjiru",
  "country_code": "KE",
  "employment_status": "SALARIED"
}
FieldNotes
external_referenceRequired. Your ID for the borrower.
full_nameRequired. Up to 200 characters.
country_codeRequired. ISO 3166-1 alpha-2, e.g. KE.
borrower_typeINDIVIDUAL (default) or BUSINESS.
employment_statusSALARIED, SELF_EMPLOYED or INFORMAL.
email, phone, date_of_birth, city, employer_nameOptional. date_of_birth must be in the past.

Applications

An application is one loan request from a borrower. New applications start as SUBMITTED.

POST /api/v1/applications
application/json
{
  "borrower_id": "<borrower id>",
  "amount": "150000.00",
  "currency": "KES",
  "term_months": 12,
  "purpose": "Working capital"
}
FieldNotes
borrower_idRequired. A borrower in the same environment.
amountRequired. A decimal string greater than zero.
term_monthsRequired. 1 to 360.
currencyISO 4217 code. Defaults to your institution's currency.
purpose, external_referenceOptional.

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).

POST /api/v1/assessments
application/json
{
  "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

FieldNotes
monthly_income, monthly_expensesDecimal strings.
existing_monthly_debtCurrent monthly repayments on existing credit.
employment_typeSALARIED, SELF_EMPLOYED, INFORMAL or UNEMPLOYED.
employment_monthsMonths at the current income source, 0 to 720.
recent_credit_inquiriesNumber of recent credit inquiries.
has_prior_defaulttrue 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.

FieldMeaning
risk_score300–850. Higher means lower risk.
risk_bandLOW (≥720), MEDIUM (650–719), HIGH (580–649), VERY_HIGH (<580)
probability_of_defaultEstimated probability of default, 0–1
affordability_ratioTotal monthly debt service including the new loan, divided by income
recommended_loan_amountThe most the affordability model supports, never above the request
risk_factorsWhat drove the score, most influential first
modelName and version of the engine that produced the result
inputsThe exact inputs the engine used
The current engine, 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 are snake_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).

List response
200 OK
{
  "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 response
422
{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "request_id": "req_4c1e…",
    "details": [
      {
        "loc": [
          "body",
          "amount"
        ],
        "msg": "…",
        "type": "…"
      }
    ]
  }
}
HTTPcodeMeaning
400bad_requestMalformed request
401unauthenticatedMissing, invalid, expired or revoked key
403forbiddenThe key cannot call this endpoint
404not_foundDoes not exist, or belongs to another institution or environment
409conflictDuplicate external_reference, or an invalid state change
422validation_errorInput failed validation; see details
429rate_limitedToo many requests; wait for Retry-After
500internal_errorUnexpected error; quote the request_id to support
503service_unavailableA 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.

Request access