Partner API Guide — Veqtrx

Veqtrx Partner API — Integration Guide

The Veqtrx Partner API is a server-to-server affordability assessment service for banks, fintechs, and lenders. You send six months of transactions plus a customer’s declared circumstances; we return an FCA-compliant affordability assessment with risk and vulnerability flags.


1. Getting started

  1. Tell us the legal name of the entity, a contact email, and the tier (pilot / standard / enterprise).
  2. We provision a partner record and return a single api_key along with an api_key_prefix (e.g. prt_acmebank_). The key is shown exactly once — store it securely.
  3. Optionally register a webhook_url. We return a webhook_secret used to HMAC-sign every delivery (see §7).

2. Authentication

Every authenticated request must include:

X-Partner-Key: prt_<your_slug>_<random>

We never accept partner keys from a browser; this API is meant for your backend systems only. Keys are bcrypt-hashed on our side — losing it means rotating it (we can’t recover it).

Rotation

Request a rotation key via your account manager. The old key keeps working until you explicitly ask us to disable it, so you can cut over without downtime.


3. Endpoint reference

POST /assess

Run an affordability assessment for one customer.

Request body (application/json):

Field Type Description
customer_ref string Your internal customer ID (opaque to us). Required.
partner_ref string Optional reference for this specific assessment (e.g. loan-app ID).
transactions array 1 – 10,000 transactions. See schema below.
circumstances object Housing, household composition, vulnerabilities.
declared_income number Self-declared monthly income (GBP). Optional but recommended.
callback_url string Override the partner default webhook for this single call.

Transaction schema:

Field Type Description
date string ISO 8601 (YYYY-MM-DD).
amount number Positive = spending, negative = income/refund (Plaid convention).
merchant_name string Merchant or counterparty name.
description string Free-text raw description (optional).
category_hint string Plaid personal_finance_category.primary if you have one. Skips our merchant-intel enrichment.
account_id string Source account; defaults to "default".

Circumstances schema:

Field Type Allowed
housing string renter, homeowner_mortgage, homeowner_outright, social_housing
household_adults integer 1 – 10
children_u16 integer 0 – 10
children_16_18 integer 0 – 10
vulnerabilities array Free-form codes from the FCA vulnerability taxonomy.

Response (200 OK):

{
  "assessment_id": "AF-A3F9B2C1D4E5",
  "partner_ref": "loan-app-12345",
  "customer_ref": "cust-001",
  "created_at": "2026-05-13T12:34:56.789Z",
  "monthly_income": 2200.0,
  "monthly_expenditure": 1820.0,
  "disposable_income": 380.0,
  "affordable_amount": 380.0,
  "affordability_score": 17,
  "categories": {
    "Food & Housekeeping": 540.0,
    "Transport": 210.0
  },
  "fca_compliant": true,
  "risk_flags": [],
  "vulnerability_flags": [],
  "transactions_analyzed": 412,
  "confidence_score": 0.97,
  "months_analyzed": 6.0,
  "processing_ms": 91
}

GET /assessment/{assessment_id}

Retrieve a previously generated assessment. Returns 404 (not 403) if the ID belongs to a different partner.

GET /usage

Current month usage and quota. Does not consume quota itself.

{
  "partner_id": "acmebank",
  "tier": "pilot",
  "monthly_quota": 1000,
  "current_usage": 412,
  "remaining": 588,
  "reset_date": "1st of next month"
}

GET /health

Unauthenticated. Returns {"status": "ok", "version": "1.0"}.


4. Error codes

Status Meaning
400 Validation error (bad housing value, malformed transaction, etc.)
401 Missing / invalid / disabled API key
404 Assessment not found (or owned by a different partner)
429 Monthly quota exhausted
500 Engine error — try again. Persistent 500s should be escalated.

All error responses follow {"detail": "<message>"}.


5. Rate limits and quotas


6. Sandbox vs production

Every partner row is created with is_test_mode = true and is moved to production after a sign-off call. Test-mode assessments still run the real engine but are flagged in our admin dashboard.


7. Webhooks

If you configured a webhook_url, we POST the assessment payload (same shape as the /assess response) to that URL after each successful assessment. Delivery is fire-and-forget with 3 retries and exponential backoff (1s, 2s, 4s).

Headers we send:

Content-Type: application/json
X-Veqtrx-Signature: sha256=<hex>
X-Veqtrx-Timestamp: <unix-seconds>
User-Agent: Veqtrx-Webhooks/1.0

Verifying the signature (Python):

import hmac, hashlib

expected = "sha256=" + hmac.new(
    webhook_secret.encode(),
    raw_request_body,        # bytes, *exact* body — do not re-serialise
    hashlib.sha256,
).hexdigest()
assert hmac.compare_digest(expected, request.headers["X-Veqtrx-Signature"])

Reject any request where the signature doesn’t match, or where the timestamp is older than your tolerance window (we recommend 5 minutes).


8. Best practices

  1. Hint when you have categories. If you already have Plaid PFC categories, pass them in category_hint. We skip our merchant-intel enrichment, which is the slow path.
  2. Send the full 6 months. The engine’s pay-frequency detection, gambling churn analysis, and refund netting all want at least 90 days; 180 days is ideal.
  3. Store assessment_id on your side. That’s how you re-fetch via /assessment/{id} and how we cross-reference issues in support.
  4. Verify webhook signatures. We rotate webhook_secret only on request, so the same secret protects all deliveries.
  5. Don’t put API keys in client-side code. This is a server-to- server API.

9. SDK examples

Python (requests)

import requests

resp = requests.post(
    "https://veqtrx-staging-backend.onrender.com/api/partner/v1/assess",
    headers={"X-Partner-Key": os.environ["VEQTRX_PARTNER_KEY"]},
    json={
        "customer_ref": "cust-001",
        "partner_ref": "loan-app-12345",
        "transactions": transactions,
        "circumstances": {
            "housing": "renter",
            "household_adults": 1,
            "children_u16": 0,
            "children_16_18": 0,
            "vulnerabilities": [],
        },
        "declared_income": 2200.0,
    },
    timeout=10.0,
)
resp.raise_for_status()
assessment = resp.json()

Node.js (fetch)

const resp = await fetch(
  "https://veqtrx-staging-backend.onrender.com/api/partner/v1/assess",
  {
    method: "POST",
    headers: {
      "X-Partner-Key": process.env.VEQTRX_PARTNER_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      customer_ref: "cust-001",
      partner_ref: "loan-app-12345",
      transactions,
      circumstances: {
        housing: "renter",
        household_adults: 1,
        children_u16: 0,
        children_16_18: 0,
        vulnerabilities: [],
      },
      declared_income: 2200.0,
    }),
  },
);
if (!resp.ok) throw new Error(await resp.text());
const assessment = await resp.json();

Architecture B — White-Label Portal

Architecture B is for partners who’d rather have us host the customer-facing flow than build it themselves. Your servers never touch the customer’s bank credentials; we operate the journey on our infrastructure under your brand and notify you via webhook when the assessment is complete.

When to choose Architecture B

Architecture A (Direct API) Architecture B (Portal)
Bank holds raw transactions Yes — you POST them No — Veqtrx fetches via Plaid
Bank builds the UI Yes No — we host it under your brand
Customer signup needed No Yes (lightweight)
Result delivery Synchronous JSON Webhook + portal redirect

You can use both for the same partner: provision once, use either flow per customer.

1. One-time setup

Ask us to enable portal mode on your partner record. You’ll need to supply:

Admins flip these via PUT /api/admin/partners/{partner_id}/portal. Once portal_enabled=true, you can mint links.

curl -X POST https://veqtrx-backend.onrender.com/api/admin/partners/<id>/portal/links \
  -H "Authorization: Bearer <admin_jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_ref": "cust-001",
    "prefilled_name": "Alex Customer",
    "prefilled_email": "alex@example.com",
    "redirect_url": "https://bank.example.com/return",
    "ttl_hours": 24
  }'

Response (returned exactly once):

{
  "session_id": "uuid",
  "partner_id": "yourbank",
  "url": "https://affordiffy.co.uk/p/yourbank?t=<token>",
  "token": "<token>",
  "expires_at": "2026-05-14T12:00:00Z",
  "important": "Save the URL or token now — it will not be shown again."
}

The token is bcrypt-hashed on our side; we cannot replay it for you if it’s lost. Mint a new link instead.

3. Send the customer to the URL

Drop them into the url above (SMS, email, dashboard handoff — your call). They land on a branded landing page that:

  1. Validates the token (we 404 if it’s invalid, used, expired, or belongs to a different partner — same shape every time to prevent slug enumeration).
  2. Shows your branding (display_name, logo, primary color).
  3. After “Start affordability check,” runs the standard 6-month assessment journey: questionnaire → Plaid connect → instant FCA-compliant report.

4. We notify you

When the customer finishes, we:

  1. Mark the portal session completed (the token cannot be re-used).
  2. Link the resulting BudgetReport to your partner row.
  3. Fire your webhook with this payload:
{
  "event": "portal.assessment_completed",
  "partner_id": "yourbank",
  "session_id": "<session_uuid>",
  "customer_ref": "cust-001",
  "assessment_id": "AF-A3F9B2C1D4E5",
  "completed_at": "2026-05-13T14:21:33Z"
}

Same HMAC signing as Architecture A — verify X-Veqtrx-Signature before trusting the payload.

  1. Redirect the customer to the redirect_url you supplied at link creation (if any). If you didn’t supply one, they’re sent to a generic “thanks” page.

5. Pull the full assessment

Once you have an assessment_id from the webhook, fetch it via the Architecture A endpoint with your partner API key:

GET /api/partner/v1/assessment/{assessment_id}
X-Partner-Key: <your_key>

You get the same AssessmentResponse shape Architecture A produces.

6. Security notes