Reference
API reference
Every endpoint, object and convention of the Harbour API v1.
JSON in, JSON out, UTF-8. Money is always integer kobo; times are ISO 8601 in UTC. If you write PHP, Harbour Connect wraps all of this.
| Environment | Base URL |
|---|---|
| Production | https://api.harbour.africa/v1 |
| Staging | https://api.staging.harbour.africa/v1 |
| Local | http://api.harbour.localhost:8080/v1 or http://localhost/harbour-api/v1 |
Authentication
GET /v1/transactions?limit=25 HTTP/1.1
Host: api.harbour.africa
Authorization: Bearer hb_sk_live_your_key
Accept: application/json
| Credential | Format | Used by |
|---|---|---|
| Secret key | hb_sk_live_… / hb_sk_test_… | Your server. Never a browser or an app. |
| Public key | hb_pk_live_… / hb_pk_test_… | harbour.js, to create a checkout only. |
| User token | hat_… access, hrt_… refresh | The Harbour mobile app and first-party clients. |
Every response carries X-Harbour-Tenant: ten_…, and every list body repeats it as
tenant_id. Keys are made in the dashboard under Settings → Developers; see
Keys and modes.
Conventions
- Idempotency. Every
POSTthat creates something acceptsIdempotency-Key(1 to 120 printable ASCII). The same key with the same body replays the first answer; with a different body it is409 idempotency_conflict. - Pagination. Lists take
limit(default 25, max 100) andcursor. - Errors. Always the same shape, with a plain-language
message. - Modes. A key only sees its own mode's data, end to end.
{
"data": [ { "object": "transaction", "id": "txn_…" } ],
"next_cursor": "eyJvIjoiMjAyNi0wOS0xM1QxNDozMjowN1oiLCJpIjoidHhuXzAxayJ9",
"tenant_id": "ten_01m2e48qxzwqrwz0axj3r20jpv"
}
{
"error": {
"code": "validation_failed",
"message": "Amount must be at least ₦100.",
"fields": { "amount_kobo": "Amount must be at least ₦100." }
}
}
Charges
| Endpoint | Body / query | Answers |
|---|---|---|
POST /v1/charges |
amount_kobo*, email*, name, phone, reference, description, return_url, provider (auto|paystack|flutterwave|opay), metadata |
201 Checkout |
GET /v1/charges/{reference} |
The Harbour HB-… reference |
Checkout, with transaction set once paid. Asks the provider when not yet final. |
GET /v1/charges |
merchant_reference=order-1043 |
The charge for your own reference, or 404. |
POST /v1/charges
Authorization: Bearer hb_sk_live_…
Content-Type: application/json
Idempotency-Key: order-1043-attempt-1
{
"amount_kobo": 1200000,
"email": "funke@example.ng",
"name": "Funke Adeyemi",
"phone": "+2348031234567",
"reference": "order-1043",
"description": "Aso-ebi order, 12 yards",
"return_url": "https://shop.example.ng/thanks",
"provider": "auto",
"metadata": { "order_id": "1043" }
}
{
"object": "checkout",
"id": "ct_01k5m0r3w9a2c8n7xq4v6t1b0d",
"reference": "HB-88413",
"merchant_reference": "order-1043",
"status": "open",
"amount_kobo": 1200000,
"currency": "NGN",
"checkout_url": "https://pay.harbour.africa/c/ct_01k5m0r3w9a2c8n7xq4v6t1b0d",
"checkout_token": "ct_01k5m0r3w9a2c8n7xq4v6t1b0d",
"provider": "paystack",
"expires_at": "2026-09-13T15:32:07Z",
"mode": "live",
"transaction": null
}
Refunds
| Endpoint | Body | Answers |
|---|---|---|
POST /v1/refunds | reference*, amount_kobo (default: all of it), reason* | 202 Refund, status requested |
GET /v1/refunds/{id} | — | Refund |
POST /v1/refunds
{
"reference": "HB-88402",
"amount_kobo": 250000,
"reason": "Two metres short on delivery"
}
→ 202 Accepted
{ "object": "refund", "id": "ref_…", "status": "requested", "amount_kobo": 250000 }
Refunds wait for an Owner to approve them; see Refunds.
Payment links
| Endpoint | Body | Answers |
|---|---|---|
POST /v1/payment-links | amount_kobo*, description*, customer, expires_in_days (1–90, default 7), provider | 201 Payment link |
GET /v1/payment-links/{id} | — | Payment link |
POST /v1/payment-links
{
"amount_kobo": 1200000,
"description": "Aso-ebi order, 12 yards",
"customer": { "name": "Funke Adeyemi", "phone": "+2348031234567" },
"expires_in_days": 7,
"provider": "auto"
}
→ 201 Created
{ "object": "payment_link", "id": "lnk_…", "url": "https://pay.harbour.africa/okafor-textiles/k2m9x", "status": "active" }
Transactions
| Endpoint | Query |
|---|---|
GET /v1/transactions |
status, provider, channel, from, to (ISO dates, local days), customer (email), limit, cursor |
GET /v1/transactions/{id} |
A txn_… id or an HB-… reference |
Objects
| Object | Fields worth knowing |
|---|---|
transaction | id, reference, merchant_reference, type, status, provider, provider_reference, amount_kobo, fee_kobo, net_kobo, refunded_kobo, currency, channel, customer, failure_message, occurred_at, settled_at, settlement_id, recovered, metadata, mode |
checkout | reference, status, checkout_url, checkout_token, amount_kobo, expires_at, transaction |
refund | id, reference, transaction_id, amount_kobo, reason, status |
payment_link | id, url, amount_kobo, description, status, expires_at, transaction_id |
settlement | id, provider, status, expected_date, expected_amount_kobo, landed_amount_kobo, shortfall_kobo, fee_kobo, transaction_count, bank, landed_at |
Statuses: a charge is initiated → pending → succeeded → settled,
or failed; later it may become refunded or disputed. Channels are
card, bank_transfer, ussd, qr, mobile_money and
other.
Rate limits
- 300 requests a minute per secret key.
- 600 a minute per user token.
- Over the limit is
429 rate_limitedwithRetry-After. The SDK waits and retries twice.
Webhook deliveries to your server are not rate limited by you; they retry at 1m, 5m, 30m, 2h, 6h and 12h for up to 24 hours. See Webhooks.