EBITS Pay REST API (v1)
EBITS Pay has two layers:
| Layer | What it is | Who uses it |
|---|---|---|
| Web portal (front end) | The screens your staff use in a browser (Livewire). Each client sees its own logo, name and colours. | Administrators, treasurers, secretaries |
| REST API (back end) | JSON endpoints at /api/v1. They use the same database, permissions, payment rules and audit trail as the portal. |
Mobile apps, partner systems (HR/payroll), a future separate front end, scripts |
Both layers run in the same Laravel application, so there is nothing extra to host. Anything done through the API shows up straight away in the portal and its audit trail, and the other way round.
Base URL: https://<your-domain>/api/v1. All responses are JSON.
1. Authentication
You can get a token in two ways:
- From the portal: go to My profile → API access tokens → Create token. The token is shown once, so copy it then.
- From an app: sign in with the user's email and password:
POST /api/v1/auth/token
Content-Type: application/json
{ "email": "treasurer@ktwa.org", "password": "••••••••", "device_name": "Finance app (Android)" }
201 Created
{
"token": "ebp_4Fq…", // keep it secret; it is shown only this once
"token_type": "Bearer",
"expires_in_days": 90,
"user": { "name": "Kwame Asante", "role": "Administrator", "permissions": ["members.view", "…"],
"organisation": { "name": "…", "branding": { "logo_url": "…", "brand_color": "#0e7490", "palette": { "50": "#…", "…": "…" } } } }
}
Send the token on every request:
Authorization: Bearer ebp_4Fq…
Accept: application/json
- Permissions: a token can do exactly what its user's role allows, nothing more. Changing the role changes what the token can do.
- Login limits: 5 failed sign-ins per email per 5 minutes. Authenticated calls are limited to 120 per minute per user.
- When a token stops working: revoking it (
DELETE /auth/token, or Revoke on the profile page), when it expires, when the user is deactivated or the organisation is suspended, and when an administrator resets the user's password. - Temporary passwords: a user who still has a temporary password must sign in to the portal and choose a new password before the API will accept them (
403). - Only a hash is stored. EBITS staff cannot read your token.
EBITS platform users
EBITS (super-admin) accounts are not part of any client. To work inside a client, add the header X-Organisation: <organisation id or slug>. The role must include Open client portals.
2. Endpoints
| Method | Path | Permission | Purpose |
|---|---|---|---|
| POST | /auth/token |
– | Sign in and get a token |
| DELETE | /auth/token |
– | Revoke the token used in this request |
| GET | /me |
– | The signed-in user, their permissions and their organisation's branding |
| GET | /organisations/{slug}/branding |
public | Logo, name and colour palette, so an app can theme its sign-in screen |
| GET | /dashboard |
dashboard.view | Members, this month's collections, all-time totals, pending payments |
| GET | /members |
members.view | List members. Filters: search, group_id, status, per_page (max 100), page |
| GET | /members/{id or Member ID} |
members.view | One member, including total_paid |
| POST | /members |
members.create | Add a member |
| PATCH | /members/{id or Member ID} |
members.edit | Change only the fields you send |
| DELETE | /members/{id or Member ID} |
members.delete | Remove a member (their payment history is kept) |
| GET | /groups |
members.view | Member groups and how many members each has |
| GET | /payment-types |
payment_types.view | Dues, levies, items and their amounts/rules |
| GET | /payments |
payments.view | List payments. Filters: status, channel, member_id, payment_type_id, from, to (YYYY-MM-DD) |
| GET | /payments/{reference} |
payments.view | One payment, with its receipt link |
| POST | /payments |
payments.record | Record cash/bank, or send a Mobile Money prompt |
| GET | /reports |
per report | The reports this user may run |
| GET | /reports/{key} |
per report | A report's KPIs, chart data and tables. Filters as in the Reports centre: from, to, type, group, channel, status, member_status, period, member |
Members
The Member ID (member_no) is the member's own staff or membership number, as imported. It must be unique within the organisation. Spaces are removed and letters are upper-cased ("ghs 4471" becomes "GHS4471").
POST /api/v1/members
{ "member_no": "GHS4471", "first_name": "Ama", "last_name": "Serwaa", "phone": "0244555666",
"email": "ama@example.com", "gender": "female", "date_of_birth": "1988-04-12", "group_id": 3 }
- Required:
member_no,first_name,phone(a Ghana number). - Optional:
last_name,email,gender(male/female),date_of_birth,joined_at,status(active/inactive/suspended),group_id,location,custom_fields(object).
Payments
POST /api/v1/payments
{ "member_no": "GHS4471", "payment_type_code": "DUES", "amount": 50, "channel": "cash" }
member_idormember_nopayment_type_idorpayment_type_codechannel:cashorbankare recorded as paid immediately;momosends a prompt tophone(default: the member's phone) and stayspendinguntil Hubtel confirms it.- For per-item types (e.g. T-shirts), send
quantity; the amount is worked out for you. - Eligibility, minimum/fixed amounts and limits are checked exactly as in the portal. Breaking a rule returns
422with amessage.
3. Responses and errors
Lists are paginated:
{ "data": [ … ], "links": { "next": "…" }, "meta": { "current_page": 1, "per_page": 25, "total": 342 } }
| Status | Meaning |
|---|---|
| 200 / 201 | OK / created |
| 401 | Missing, wrong, revoked or expired token |
| 403 | The role lacks the permission, the organisation is suspended, or the password must be changed |
| 404 | Not found, or it belongs to another organisation |
| 422 | Validation failed (errors lists each field), or X-Organisation is missing for an EBITS user |
| 429 | Too many requests; wait and retry |
Every error has a message. Validation errors also have errors: { field: ["…"] }.
4. Quick test with curl
TOKEN=$(curl -s -X POST https://pay.example.com/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"email":"treasurer@ktwa.org","password":"password","device_name":"curl"}' | jq -r .token)
curl -s https://pay.example.com/api/v1/dashboard -H "Authorization: Bearer $TOKEN" | jq
curl -s "https://pay.example.com/api/v1/members?search=ama" -H "Authorization: Bearer $TOKEN" | jq
The machine-readable description is in docs/openapi.yaml. Import it into Postman, Insomnia or Swagger UI.