GP CoreBanking API

Complete API documentation for external integrations

Introduction

GP CoreBanking is a mock core banking system that GP products develop and test against. Its API covers onboarding Clients, opening Accounts, moving money and issuing Loans. It is never used for real banking.

Key Features

  • Client Management - Create and look up Retail and Corporate Clients
  • Account Management - Savings, Checking, Current and Loan Accounts, each with a single running balance
  • Moving Money - Deposits, Withdrawals and Transfers, each applied atomically in the database
  • Loans - Issue and list Loans against Loan Accounts
  • API Keys - One key per Machine client, issued and revoked by an Admin

Base URL

All API requests should be prefixed with the following base URL:

https://bkclatwirugxogrfbhno.supabase.co/functions/v1/corebanking

Every response is JSON. Successful responses are {"status": "success", "data": ...}; creates return 201, everything else 200.

Health Check

The base URL itself is a public health check (no credentials needed):

GET /

Example Response:

{
  "status": "online",
  "message": "Welcome to the GP Core Banking API",
  "version": "1.0.0",
  "timestamp": "2026-10-22T09:30:00.000Z"
}

timestamp is the server's current time.

Implementation Notes

  • The API is a Supabase Edge Function over a Postgres database
  • Each Account has a single running balance; Deposits, Withdrawals and Transfers change it and record Transactions
  • The System accounts (TELLER-ACCOUNT and VAULT-ACCOUNT) must already exist; without them Deposits and Withdrawals fail with 422 SYSTEM_ACCOUNT_MISSING
  • Account numbers are 10 random digits unless you supply your own

Authentication

Every endpoint except the health check needs credentials: an API key or a staff login token. Without valid credentials you get 401 UNAUTHORIZED.

Option 1: API Key (Machine clients)

Send your key in the x-api-key header:

curl "https://bkclatwirugxogrfbhno.supabase.co/functions/v1/corebanking/clients" \
  -H "x-api-key: sk_live_your-api-key"
  • An Admin issues one key per Machine client from the API keys page of the web app (or POST /api_keys).
  • The key is shown once, when it is issued. Copy it then: only its hash is stored, so it cannot be shown again.
  • An Admin can revoke a key; it fails with 401 from the next request on. Lost a key? Ask for it to be revoked and a new one issued.
  • An API key can call every endpoint except the Admin-only /api_keys endpoints.
  • The Supabase apikey header is ignored; only x-api-key counts.

Option 2: Staff login token (the web app)

Send a Staff user's Supabase Auth access token as a bearer token:

curl "https://bkclatwirugxogrfbhno.supabase.co/functions/v1/corebanking/clients" \
  -H "Authorization: Bearer your-staff-access-token"

A Staff user is a Teller or an Admin. Both can call every endpoint; only an Admin can call /api_keys. If both headers are sent, x-api-key is used.

API Endpoints

Clients

GET /clients

List Clients.

Query Parameters:
  • client_type (optional) - Exact match: "Retail" or "Corporate"
  • ussd_phone_number (optional) - Partial match on the USSD phone number
View Example Response (200)
{
  "status": "success",
  "data": [
    {
      "client_id": "a0000000-0000-0000-0000-000000000002",
      "client_type": "Retail",
      "first_name": "Kofi",
      "last_name": "Mensah",
      "company_name": null,
      "registration_number": null,
      "contact_number": "233241000001",
      "email": "kofi.mensah@example.com",
      "address": "Osu, Accra",
      "ussd_phone_number": "233241000001",
      "ussd_status": "active",
      "created_at": "2026-10-01T08:00:00+00:00",
      "updated_at": "2026-10-01T08:00:00+00:00"
    }
  ]
}

GET /clients/:id

Get one Client.

Path Parameters:
  • id - The client_id (UUID)
  • Unknown id: 404 NOT_FOUND. Malformed id: 422 VALIDATION_FAILED.
View Example Response (200)
{
  "status": "success",
  "data": {
    "client_id": "a0000000-0000-0000-0000-000000000002",
    "client_type": "Retail",
    "first_name": "Kofi",
    "last_name": "Mensah",
    "company_name": null,
    "registration_number": null,
    "contact_number": "233241000001",
    "email": "kofi.mensah@example.com",
    "address": "Osu, Accra",
    "ussd_phone_number": "233241000001",
    "ussd_status": "active",
    "created_at": "2026-10-01T08:00:00+00:00",
    "updated_at": "2026-10-01T08:00:00+00:00"
  }
}

GET /clients/ussd/search

Find the Client registered for a USSD phone number.

Query Parameters:
  • ussd_phone (required) - Exact match on the USSD phone number
  • Missing ussd_phone: 422 VALIDATION_FAILED. No match: 404 NOT_FOUND.
View Example Response (200)
{
  "status": "success",
  "data": {
    "client_id": "a0000000-0000-0000-0000-000000000002",
    "client_type": "Retail",
    "first_name": "Kofi",
    "last_name": "Mensah",
    "company_name": null,
    "registration_number": null,
    "contact_number": "233241000001",
    "email": "kofi.mensah@example.com",
    "address": "Osu, Accra",
    "ussd_phone_number": "233241000001",
    "ussd_status": "active",
    "created_at": "2026-10-01T08:00:00+00:00",
    "updated_at": "2026-10-01T08:00:00+00:00"
  }
}

POST /clients

Create a Client.

Request Body (JSON):
  • client_type (required) - "Retail" or "Corporate"
  • first_name, last_name (Retail only) - Required for Retail, must be absent for Corporate
  • company_name, registration_number (Corporate only) - Required for Corporate, must be absent for Retail
  • contact_number (required) - Digits only (no + or spaces), at least 5
  • email (required) - A valid email address
  • address (required) - Non-empty
  • ussd_phone_number (optional) - Digits only
{
  "client_type": "Retail",
  "first_name": "Ama",
  "last_name": "Boateng",
  "contact_number": "233241000009",
  "email": "ama.boateng@example.com",
  "address": "Labone, Accra",
  "ussd_phone_number": "233241000009"
}
  • Breaking the Retail/Corporate field rules or leaving out address: 422 VALIDATION_FAILED, with errors[] naming each field.
  • Other fields (such as ussd_status) are ignored.
View Example Response (201)
{
  "status": "success",
  "data": {
    "client_id": "a0000000-0000-0000-0000-000000000009",
    "client_type": "Retail",
    "first_name": "Ama",
    "last_name": "Boateng",
    "company_name": null,
    "registration_number": null,
    "contact_number": "233241000009",
    "email": "ama.boateng@example.com",
    "address": "Labone, Accra",
    "ussd_phone_number": "233241000009",
    "ussd_status": null,
    "created_at": "2026-10-01T08:00:00+00:00",
    "updated_at": "2026-10-01T08:00:00+00:00"
  }
}

Accounts

GET /accounts

List Accounts, including the System accounts, with the owning Client's name.

Query Parameters:
  • client_id (optional) - Filter by Client
  • account_type (optional) - "Savings", "Checking", "Current" or "Loan"
  • status (optional) - "Active", "Inactive" or "Frozen"
  • currency (optional) - Currency code, e.g. "GHS"
View Example Response (200)
{
  "status": "success",
  "data": [
    {
      "account_id": "b0000000-0000-0000-0000-000000000003",
      "account_number": "1000000001",
      "client_id": "a0000000-0000-0000-0000-000000000002",
      "account_type": "Savings",
      "currency": "GHS",
      "balance": 2500,
      "status": "Active",
      "created_at": "2026-10-01T08:00:00+00:00",
      "updated_at": "2026-10-01T08:00:00+00:00",
      "clients": {
        "first_name": "Kofi",
        "last_name": "Mensah"
      }
    }
  ]
}

GET /accounts/:id

Get one Account, with its Client's details. Use this to read a balance.

Path Parameters:
  • id - The account_id (UUID), not the account number
  • Unknown id: 404 NOT_FOUND. Malformed id: 422 VALIDATION_FAILED.
View Example Response (200)
{
  "status": "success",
  "data": {
    "account_id": "b0000000-0000-0000-0000-000000000003",
    "account_number": "1000000001",
    "client_id": "a0000000-0000-0000-0000-000000000002",
    "account_type": "Savings",
    "currency": "GHS",
    "balance": 2500,
    "status": "Active",
    "created_at": "2026-10-01T08:00:00+00:00",
    "updated_at": "2026-10-01T08:00:00+00:00",
    "clients": {
      "first_name": "Kofi",
      "last_name": "Mensah",
      "company_name": null,
      "client_type": "Retail",
      "email": "kofi.mensah@example.com",
      "contact_number": "233241000001",
      "address": "Osu, Accra"
    }
  }
}

POST /accounts

Open an Account for a Client.

Request Body (JSON):
  • client_id (required) - UUID of an existing Client
  • account_type (required) - "Savings", "Checking", "Current" or "Loan"
  • currency (optional) - 3-letter code, default "GHS"
  • account_number (optional) - Digits only, at least 10. If omitted, a random 10-digit number is generated
  • balance (optional) - Opening balance, not negative, default 0
{
  "client_id": "a0000000-0000-0000-0000-000000000002",
  "account_type": "Savings",
  "currency": "GHS"
}
  • Unknown client_id: 422 VALIDATION_FAILED. An account_number that is already taken: 409 CONFLICT.
  • New Accounts start Active. Other fields (such as status) are ignored.
View Example Response (201)
{
  "status": "success",
  "data": {
    "account_id": "b0000000-0000-0000-0000-000000000009",
    "account_number": "4821937605",
    "client_id": "a0000000-0000-0000-0000-000000000002",
    "account_type": "Savings",
    "currency": "GHS",
    "balance": 0,
    "status": "Active",
    "created_at": "2026-10-01T08:00:00+00:00",
    "updated_at": "2026-10-01T08:00:00+00:00"
  }
}

Transactions

GET /transactions

List Transactions, newest first.

Query Parameters:
  • account_id (optional) - Filter by Account
  • client_id (optional) - Filter by the Account's Client
  • transaction_type (optional) - "Deposit", "Withdrawal", "Transfer" or "Loan Payment"
  • limit (optional) - Maximum rows, default 50
View Example Response (200)
{
  "status": "success",
  "data": [
    {
      "transaction_id": "e0000000-0000-0000-0000-000000000001",
      "account_id": "b0000000-0000-0000-0000-000000000003",
      "transaction_type": "Deposit",
      "amount": 100,
      "currency": "GHS",
      "transaction_date": "2026-10-22T09:30:00+00:00",
      "description": "Cash deposit",
      "reference": "DEP-1761125400000",
      "status": "Completed",
      "accounts": {
        "client_id": "a0000000-0000-0000-0000-000000000002"
      }
    }
  ]
}

POST /transactions

Make a Deposit or Withdrawal on an Account.

Request Body (JSON):
  • account_id (required) - UUID of the Account
  • transaction_type (required) - "Deposit" or "Withdrawal"
  • amount (required) - Number greater than 0
  • currency (required) - The Account's currency (3-letter code, case-insensitive)
  • description (optional) - Up to 255 characters
  • reference (optional) - Letters, digits, - and _ only. Default DEP-<ms> or WDR-<ms>
{
  "account_id": "b0000000-0000-0000-0000-000000000003",
  "transaction_type": "Deposit",
  "amount": 100,
  "currency": "GHS",
  "description": "Cash deposit",
  "reference": "DEP-0001"
}
  • Withdrawal over the balance: 422 INSUFFICIENT_FUNDS, and the balance is unchanged.
  • A currency other than the Account's: 422 CURRENCY_MISMATCH, and the balance is unchanged.
  • Unknown Account: 404 NOT_FOUND. Missing System account: 422 SYSTEM_ACCOUNT_MISSING.
View Example Response (201)
{
  "status": "success",
  "data": {
    "transaction_id": "e0000000-0000-0000-0000-000000000001",
    "account_id": "b0000000-0000-0000-0000-000000000003",
    "amount": 100,
    "new_balance": 2600,
    "reference": "DEP-0001"
  }
}

POST /transfers

Transfer money between two Accounts in the same currency.

Request Body (JSON):
  • from_account_id (required) - UUID of the source Account
  • to_account_id (required) - UUID of the destination Account, not the source
  • amount (required) - Number greater than 0
  • currency (required) - Must match both Accounts' currency
  • description (optional) - Free text
{
  "from_account_id": "b0000000-0000-0000-0000-000000000003",
  "to_account_id": "b0000000-0000-0000-0000-000000000004",
  "amount": 25,
  "currency": "GHS",
  "description": "Rent"
}
  • The response describes the debit Transaction on the source Account (negative amount). The credit on the destination has reference <reference>-CREDIT.
  • Errors: 422 SAME_ACCOUNT, 422 CURRENCY_MISMATCH, 422 INSUFFICIENT_FUNDS, 404 NOT_FOUND.
View Example Response (201)
{
  "status": "success",
  "data": {
    "transaction_id": "e0000000-0000-0000-0000-000000000002",
    "account_id": "b0000000-0000-0000-0000-000000000003",
    "amount": -25,
    "reference": "TXF-1761125400000"
  }
}

Loans

GET /loans

List Loans with their Account and Client.

View Example Response (200)
{
  "status": "success",
  "data": [
    {
      "loan_id": "c0000000-0000-0000-0000-000000000001",
      "account_id": "b0000000-0000-0000-0000-000000000006",
      "loan_amount": 10000,
      "interest_rate": 24,
      "start_date": "2026-10-01",
      "end_date": "2027-10-01",
      "repayment_frequency": "Monthly",
      "outstanding_balance": 10000,
      "status": "Active",
      "loan_type": "STANDARD",
      "accounts": {
        "account_number": "1000000004",
        "balance": 0,
        "clients": {
          "first_name": "Kofi",
          "last_name": "Mensah",
          "company_name": null,
          "client_type": "Retail"
        }
      }
    }
  ]
}

GET /loans/:id

Get one Loan with its Account and Client.

Path Parameters:
  • id - The loan_id (UUID)
  • Unknown id: 404 NOT_FOUND. Malformed id: 422 VALIDATION_FAILED.
View Example Response (200)
{
  "status": "success",
  "data": {
    "loan_id": "c0000000-0000-0000-0000-000000000001",
    "account_id": "b0000000-0000-0000-0000-000000000006",
    "loan_amount": 10000,
    "interest_rate": 24,
    "start_date": "2026-10-01",
    "end_date": "2027-10-01",
    "repayment_frequency": "Monthly",
    "outstanding_balance": 10000,
    "status": "Active",
    "loan_type": "STANDARD",
    "accounts": {
      "account_number": "1000000004",
      "balance": 0,
      "clients": {
        "first_name": "Kofi",
        "last_name": "Mensah",
        "company_name": null,
        "client_type": "Retail"
      }
    }
  }
}

POST /loans

Issue a Loan against an Account (normally a Loan-type Account; not enforced). Records the Loan only; no money moves.

Request Body (JSON):
  • account_id (required) - UUID of the Account
  • loan_amount (required) - Number greater than 0
  • interest_rate (required) - Annual rate in percent: 24 means 24%. At least 0 and below 100
  • start_date, end_date (required) - Dates (YYYY-MM-DD); end_date must be after start_date
  • outstanding_balance (required) - Number, not negative
  • repayment_frequency (optional) - "Daily", "Weekly", "Monthly", "Quarterly" or "Yearly"; default "Monthly"
  • loan_type (optional) - "STANDARD" or "BCB_TRADE_LOAN"; default "STANDARD"
{
  "account_id": "b0000000-0000-0000-0000-000000000006",
  "loan_amount": 10000,
  "interest_rate": 24,
  "start_date": "2026-10-01",
  "end_date": "2027-10-01",
  "repayment_frequency": "Monthly",
  "outstanding_balance": 10000
}
  • Invalid dates or rate: 422 VALIDATION_FAILED, with errors[] naming the field. Unknown account_id: 422 VALIDATION_FAILED.
View Example Response (201)
{
  "status": "success",
  "data": {
    "loan_id": "c0000000-0000-0000-0000-000000000001",
    "account_id": "b0000000-0000-0000-0000-000000000006",
    "loan_amount": 10000,
    "interest_rate": 24,
    "start_date": "2026-10-01",
    "end_date": "2027-10-01",
    "repayment_frequency": "Monthly",
    "outstanding_balance": 10000,
    "status": "Active",
    "loan_type": "STANDARD"
  }
}

API keys (Admin only)

Only an Admin's staff login can call these. A Teller or an API key gets 403 FORBIDDEN.

GET /api_keys

List API keys, newest first. Metadata only: no endpoint ever returns a stored key.

View Example Response (200)
{
  "status": "success",
  "data": [
    {
      "id": "d0000000-0000-0000-0000-000000000001",
      "name": "USSD Designer",
      "key_prefix": "sk_live_3f9a",
      "status": "active",
      "created_at": "2026-10-22T09:30:00+00:00",
      "created_by": "22222222-2222-2222-2222-222222222222"
    }
  ]
}

POST /api_keys

Issue an API key for a Machine client.

Request Body (JSON):
  • name (required) - The Machine client (product) the key is for
{
  "name": "USSD Designer"
}
  • key is in this response only. Copy it now: only its hash is stored.
  • Missing or blank name: 422 VALIDATION_FAILED.
View Example Response (201)
{
  "status": "success",
  "data": {
    "id": "d0000000-0000-0000-0000-000000000001",
    "name": "USSD Designer",
    "key_prefix": "sk_live_3f9a",
    "status": "active",
    "created_at": "2026-10-22T09:30:00+00:00",
    "created_by": "22222222-2222-2222-2222-222222222222",
    "key": "sk_live_3f9a…(48 hex characters in all)"
  }
}

PUT /api_keys/:id/revoke

Revoke an API key. It fails with 401 from the next request on.

Path Parameters:
  • id - The key's id (UUID)
  • Unknown id: 404 NOT_FOUND. Malformed id: 422 VALIDATION_FAILED.
View Example Response (200)
{
  "status": "success",
  "data": {
    "id": "d0000000-0000-0000-0000-000000000001",
    "name": "USSD Designer",
    "key_prefix": "sk_live_3f9a",
    "status": "revoked",
    "created_at": "2026-10-22T09:30:00+00:00",
    "created_by": "22222222-2222-2222-2222-222222222222"
  }
}

How Money Moves

Each Account has one running balance. Every money movement runs in a single database transaction: the balances change and the Transactions are recorded together, or nothing changes.

System Accounts

Two Accounts owned by the bank, which cash passes through:

  • Teller account (TELLER-ACCOUNT) - the teller's till
  • Vault account (VAULT-ACCOUNT) - the bank's cash reserve

Deposit

  1. Refused with 422 CURRENCY_MISMATCH if not in the Account's currency
  2. The Client's Account balance increases; a Deposit Transaction is recorded
  3. Three Transfer Transactions record the cash going in to the Teller account and on to the Vault account (references ending -TELLER-IN, -TELLER-OUT, -VAULT-IN)

Withdrawal

  1. Refused with 422 INSUFFICIENT_FUNDS if the amount is more than the balance; with 422 CURRENCY_MISMATCH if not in the Account's currency
  2. The Client's Account balance decreases; a Withdrawal Transaction is recorded
  3. Three Transfer Transactions record the cash going from the Vault account through the Teller account (references ending -VAULT-OUT, -TELLER-IN, -TELLER-OUT)

The System account Transactions are records only: they do not change the System accounts' balances.

Transfer

  1. Refused if the Accounts are the same, their currencies differ, or the source balance is too low
  2. The source balance decreases and the destination balance increases
  3. Two Transfer Transactions are recorded: a negative one on the source, a positive one on the destination

Error Handling

Error Shape

Every error is JSON with the same shape. Branch on code, which is stable; message is for people and may change.

{
  "status": "error",
  "code": "INSUFFICIENT_FUNDS",
  "message": "Insufficient funds"
}

A VALIDATION_FAILED for a request body also lists each problem:

{
  "status": "error",
  "code": "VALIDATION_FAILED",
  "message": "Validation failed",
  "errors": [
    {
      "path": "amount",
      "message": "Number must be greater than 0"
    }
  ]
}

Status Codes

Status Code When
401 UNAUTHORIZED No credentials, or an invalid or revoked API key, or an invalid staff token
403 FORBIDDEN Authenticated but not allowed, e.g. a Teller or an API key calling /api_keys
404 NOT_FOUND Unknown route, or no Client, Account, Loan or API key with that id
409 CONFLICT A value that must be unique is already taken, e.g. an account_number
422 VALIDATION_FAILED Invalid body, malformed JSON, malformed id, a missing required parameter, or a reference (such as client_id) to a record that does not exist. The message names the field
422 INSUFFICIENT_FUNDS A Withdrawal or Transfer is more than the Account's balance
422 CURRENCY_MISMATCH A Deposit or Withdrawal not in the Account's currency, or a Transfer between Accounts of different currencies
422 SAME_ACCOUNT A Transfer from an Account to itself
422 NON_POSITIVE_AMOUNT An amount of zero or less reached the money functions
422 SYSTEM_ACCOUNT_MISSING The TELLER-ACCOUNT or VAULT-ACCOUNT System account does not exist
500 INTERNAL_ERROR Anything unexpected (a fault on our side, not your request). The message is always "Internal server error"; the detail is only in the server log

Data Types

Client Types

  • Retail: A person
  • Corporate: A company

USSD Status

  • active: USSD banking enabled
  • inactive: USSD banking disabled

Account Types

  • Savings
  • Checking
  • Current
  • Loan: Holds a Loan

Account Status

  • Active: Default for new Accounts
  • Inactive
  • Frozen

Transaction Types

  • Deposit
  • Withdrawal
  • Transfer: One leg of a Transfer, or a System account movement
  • Loan Payment: Exists in the schema; the API does not create it

Transaction Status

  • Completed: Every Transaction the API creates
  • Pending
  • Failed

Loan Status

  • Active: Default
  • Paid Off
  • Defaulted

API Key Status

  • active
  • revoked: Fails authentication