API Reference

One endpoint. One PDF. Complete financial intelligence.

Send a bank statement. Receive a structured affordability report in under two seconds. Everything you need to integrate Sorae is on this page.

Base URL

https://sorae.co.za

Endpoint

POST /api/v1/analyze

Auth

Bearer token

Content type

multipart/form-data

Authentication

API keys.

Every request must include a valid API key in the Authorization header. Keys are created in the dashboard and shown in full exactly once — store yours securely.

Key format

Live keys are prefixed sk_live_. Test keys are prefixed sk_test_. Test keys process real PDFs but do not deduct credits.

Key security

Never expose API keys in client-side code or public repositories. All keys are hashed server-side — Sorae cannot recover a lost key. Rotate from the dashboard if a key is compromised.

Authorization header

Authorization: Bearer sk_live_your_api_key_here

The endpoint

POST /api/v1/analyze

Upload a PDF bank statement as multipart/form-data. Sorae extracts, parses, and analyses it — returning the full intelligence stack in a single response.

Request headers

FieldTypeDescription
Authorizationstring requiredBearer sk_live_… — your API key.
x-client-referencestring optionalYour own reference ID (loan ID, application number, etc). Appears in logs and is stored with the report for reconciliation.

Request body — multipart/form-data

FieldTypeDescription
fileFile requiredA PDF bank statement. Maximum 10 MB. Must be a branch-issued or internet banking PDF — not a screenshot or scanned image.

Supported banks

CapitecStandard BankFNBABSANedbank

Code examples

cURL

curl -X POST https://sorae.co.za/api/v1/analyze \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "x-client-reference: APP-00123" \
  -F "file=@/path/to/statement.pdf"

Node.js — fetch

import fs from 'fs'

const form = new FormData()
form.append('file', new Blob([fs.readFileSync('./statement.pdf')], { type: 'application/pdf' }), 'statement.pdf')

const res = await fetch('https://sorae.co.za/api/v1/analyze', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sk_live_your_api_key',
    'x-client-reference': 'APP-00123',
  },
  body: form,
})

const { success, data, error } = await res.json()

if (!success) {
  console.error(error.code, error.message)
} else {
  console.log('Grade:', data.affordability.grade)
  console.log('Max instalment:', data.affordability.recommended_max_installment)
}

Python — requests

import requests

with open('statement.pdf', 'rb') as f:
    response = requests.post(
        'https://sorae.co.za/api/v1/analyze',
        headers={
            'Authorization': 'Bearer sk_live_your_api_key',
            'x-client-reference': 'APP-00123',
        },
        files={'file': ('statement.pdf', f, 'application/pdf')},
    )

result = response.json()

if result['success']:
    data = result['data']
    print(f"Grade: {data['affordability']['grade']}")
    print(f"Max instalment: R{data['affordability']['recommended_max_installment']:,.2f}")
else:
    print(result['error']['code'], result['error']['message'])

Response schema

The full result object.

Successful responses return HTTP 200 with this envelope. Failed calls return a non-2xx status with an error object — see Error codes below.

Response headers

X-Call-ID

UUID for this request. Include in support queries.

X-Processing-Ms

Server-side processing time in milliseconds.

Success envelope

Success — HTTP 200

{
  "success": true,
  "data": { ... }   // AnalysisResult — see fields below
}

Top-level fields

FieldTypeDescription
call_idstringUnique UUID for this analysis. Matches the X-Call-ID header.
bankstringDetected bank. One of: CAPITEC, STANDARD_BANK, FNB, ABSA, NEDBANK.
statement_periodobject{ from: "YYYY-MM-DD", to: "YYYY-MM-DD" } — the date range of the statement.
incomeobjectFull income analysis. See Income fields.
expensesobjectCategorised expense breakdown. See Expense fields.
balance_behaviourobjectEnd-of-month and overdraft behaviour signals.
affordabilityobjectScore, grade, disposable income, and recommended max instalment.
risk_flagsarrayArray of detected risk signals. Empty array if none.
summarystringPlain-language analyst summary for human readers.
processed_atstringISO 8601 timestamp of when this result was generated.
processing_msnumberServer processing time in milliseconds.

income

FieldTypeDescription
verifiedbooleanWhether income could be confidently verified from transaction data.
typestringSALARY | WAGES | IRREGULAR | MIXED | UNKNOWN.
monthly_netnumberVerified average net monthly income in ZAR.
salary_datenumber|nullDay of month income typically arrives (1–31), or null if irregular.
consistency_scorenumberIncome consistency 0–100. Higher is more reliable.
months_analyzednumberNumber of calendar months covered by the statement.
income_sourcesarrayArray of identified income sources with description, frequency, and average amount.
volatilityobjectCoefficient of variation, STABLE | MODERATE | VOLATILE | HIGHLY_VOLATILE classification, and monthly breakdown.
deposit_classificationobjectPrimary source type (EMPLOYER_PAYROLL, INFORMAL_EFT, etc.) with ratios.

expenses

FieldTypeDescription
total_monthlynumberAverage total monthly outflows in ZAR.
breakdownobjectCategorised monthly averages: rent_housing, debt_repayments, groceries_food, transport, subscriptions, gambling, cash_withdrawals, utilities, insurance, other.
rigidityobjectFixed vs flexible expense split, fixed_ratio (0–1), and flexibility_score (0–100).

balance_behaviour

FieldTypeDescription
average_end_of_month_balancenumberAverage closing balance at month end in ZAR.
lowest_pre_payday_balancenumberLowest balance in the 3 days before salary credit.
paycheck_to_paycheckbooleanTrue if end-of-month balance is consistently below 5% of monthly income.
overdraft_frequencynumberNumber of times the account went negative.
overdraft_depth_avgnumberAverage overdraft depth in ZAR when the account was negative.
buffer_scorenumberCash buffer health 0–100. Higher is better.
months_with_negativenumberCount of calendar months where the balance went negative at least once.

affordability

FieldTypeDescription
scorenumberComposite affordability score 0–100.
gradestringA | B | C | D | F — overall affordability grade.
disposable_incomenumberMonthly net income minus total expenses in ZAR.
recommended_max_installmentnumberMaximum recommended monthly loan instalment in ZAR. NCR-aligned.
debt_to_income_rationumberTotal debt repayments as a fraction of income (0–1).

risk_flags[ ]

FieldTypeDescription
codestringFlag identifier. See full list of flag codes below.
severitystringLOW | MEDIUM | HIGH | CRITICAL.
descriptionstringHuman-readable explanation of the flag.
occurrencesnumberNumber of times this signal was detected in the period.
amountnumber|undefinedAssociated monetary value where applicable (e.g. total gambling spend).

Example response

HTTP 200 — abbreviated

{
  "success": true,
  "data": {
    "call_id": "3f4a1c2e-8b7d-4e1a-9f0c-1234567890ab",
    "bank": "CAPITEC",
    "statement_period": { "from": "2024-10-01", "to": "2025-01-31" },
    "income": {
      "verified": true,
      "type": "SALARY",
      "monthly_net": 28500,
      "salary_date": 25,
      "consistency_score": 91,
      "months_analyzed": 4,
      "volatility": {
        "coefficient_of_variation": 4.2,
        "classification": "STABLE"
      }
    },
    "expenses": {
      "total_monthly": 19800,
      "breakdown": {
        "rent_housing": 8000,
        "debt_repayments": 4200,
        "groceries_food": 3100,
        "transport": 1800,
        "subscriptions": 650,
        "gambling": 0,
        "cash_withdrawals": 1200,
        "utilities": 580,
        "insurance": 270,
        "other": 0
      }
    },
    "affordability": {
      "score": 74,
      "grade": "B",
      "disposable_income": 8700,
      "recommended_max_installment": 4350,
      "debt_to_income_ratio": 0.15
    },
    "risk_flags": [
      {
        "code": "PRE_PAYDAY_CASH",
        "severity": "MEDIUM",
        "description": "Cash withdrawals in 3 days before salary credit.",
        "occurrences": 3,
        "amount": 2100
      }
    ],
    "summary": "Stable salaried income of R28,500/month verified across 4 months. Affordability grade B with R4,350 recommended maximum instalment. One medium-severity flag: pre-payday cash withdrawal pattern detected.",
    "processed_at": "2025-01-31T09:14:22.411Z",
    "processing_ms": 1284
  }
}

Risk flag codes

16 behavioural signals.

CodeSeverityDescription
RETURNED_DEBITHIGHOne or more debit orders were returned unpaid.
PRE_PAYDAY_CASHMEDIUMCash withdrawals in the 3 days before salary credit.
GAMBLING_DETECTEDHIGHGambling transactions identified.
IRREGULAR_INCOMEMEDIUMIncome pattern is inconsistent month-to-month.
NO_INCOME_DETECTEDCRITICALNo verifiable income found in the statement.
BALANCE_NEGATIVEHIGHAccount balance went negative during the period.
MULTIPLE_LOANSHIGHMultiple active loan repayments detected.
INCOME_UNVERIFIEDMEDIUMIncome present but could not be confidently verified.
INCOME_VOLATILITYMEDIUMHigh coefficient of variation in monthly income.
PAYDAY_LOAN_DETECTEDHIGHShort-term / payday loan activity found.
OVERDRAFT_RELIANCEHIGHAccount frequently operates in overdraft.
LIFESTYLE_INFLATIONLOWDiscretionary spending increasing relative to income.
TRANSACTION_TIMING_STRESSMEDIUMPayment timing patterns suggest cash flow stress.
HIDDEN_DEBT_SIGNALSHIGHIndirect signals of undisclosed debt obligations.
CASH_WITHDRAWAL_HIGHMEDIUMUnusually high proportion of cash withdrawals.
PAYCHECK_TO_PAYCHECKHIGHEnd-of-month balance consistently below 5% of income.

Error codes

Error responses.

All errors share the same envelope. Use error.code for programmatic handling and error.call_id when contacting support.

Error envelope

{
  "success": false,
  "error": {
    "code": "PAYMENT_REQUIRED",
    "message": "Insufficient credit balance. Top up at https://sorae.co.za/dashboard",
    "call_id": "3f4a1c2e-8b7d-4e1a-9f0c-1234567890ab"
  }
}
CodeStatusDescription
UNAUTHORIZED401Missing or invalid API key.
FORBIDDEN403Valid key but insufficient permissions.
RATE_LIMITED429Too many requests. Check Retry-After header.
PAYMENT_REQUIRED402Insufficient credit balance. Top up in the dashboard.
INVALID_REQUEST400Malformed request — missing file or wrong content type.
FILE_TOO_LARGE422PDF exceeds the 10 MB limit.
UNSUPPORTED_FORMAT422Uploaded file is not a PDF.
UNSUPPORTED_BANK422Bank not yet supported. Check the supported banks list.
PARSE_FAILED422Could not extract usable data from the document.
ANALYSIS_FAILED500AI summary generation failed.
INTERNAL_ERROR500Unexpected server error. Retry with exponential backoff.

Rate limit headers

On 429 RATE_LIMITED responses, check the Retry-After header (seconds to wait), X-RateLimit-Remaining-RPM, and X-RateLimit-Remaining-Daily to manage your retry logic.

Credits & rate limits

What gets charged. What gets throttled.

One credit per success

Credits are deducted only on HTTP 200 responses. Requests that fail at auth, parsing, or rate limiting are never charged.

Zero balance = 402

Requests made with no remaining credits return PAYMENT_REQUIRED. Top up from the dashboard to resume immediately.

Rate limits per key

Each API key has per-minute and daily request limits. Default limits are shown in the dashboard. Contact sales for higher throughput requirements.

POPIA & zero data retention

Original PDF files and raw transaction text are never persisted. All extraction and analysis happens in-memory. Once a response is returned, the document is discarded. Only non-PII metadata — bank name, statement period, processing time — is logged for audit purposes. Your clients' financial data stays yours.

Read our privacy policy →

Ready to integrate

Get your API key in two minutes.

Create a free account, generate a key from the dashboard, and run your first analysis. Three free credits included — no credit card required.