mytmn-hub
REST API that fetches, normalizes, and caches MyTaman billing data as structured JSON (dashboard, paid invoices, billing invoices, profiles). Integrate using the endpoints below; use the machine-readable OpenAPI spec or Postman collection for your stack.
OpenAPI Spec
GET /openapi.json
Postman Collection
GET /postman/collection.json
Health Check
GET /health
Release notes
GET /release-notes
Base URL
Quick start
1. Authenticate with your MyTaman credentials. You receive a short-lived access token and a long-lived refreshToken (store both server-side in your app):
# Login curl -X POST https://mytmn.sofehaus.com/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"user@example.com","password":"s3cret"}' # Response: {"token":"…","refreshToken":"…"}
2. Use the access token for API calls. If you get 401 unauthorized (expired access token), mint a new one without re-logging in:
# Refresh access token curl -X POST https://mytmn.sofehaus.com/api/auth/refresh \ -H "Content-Type: application/json" \ -d '{"refreshToken":"<paste refreshToken>"}' # Response: {"token":"…"}
3. Use the current access token for all data requests:
# Get your taman profile curl https://mytmn.sofehaus.com/api/profile \ -H "Authorization: Bearer <token>" # List all invoice profiles curl https://mytmn.sofehaus.com/api/billing/invoices \ -H "Authorization: Bearer <token>" # Paid Invoices table (optional ?page=, paid_at_*, invoice_due_*, online_offline_search) curl "https://mytmn.sofehaus.com/api/billing/paid-invoices?page=1&online_offline_search=Only+Online" \ -H "Authorization: Bearer <token>" # Billing Invoices table (optional ?page=, search params match portal Ransack keys) curl "https://mytmn.sofehaus.com/api/billing/issued-invoices?page=1&status_eq=Paid" \ -H "Authorization: Bearer <token>" # Get stats for a specific invoice curl https://mytmn.sofehaus.com/api/billing/invoices/9978/stats \ -H "Authorization: Bearer <token>"
4. Logout when done (invalidates access + refresh on the server and clears TTL cache):
curl -X POST https://mytmn.sofehaus.com/api/auth/logout \
-H "Authorization: Bearer <access token>"
API reference
GET /health
Service health check. No authentication required.
// 200 OK { "ok": true, "service": "mytmn-hub" }
POST /api/auth/login
Authenticate with MyTaman. Returns short-lived token (use as Bearer) and long-lived refreshToken. Upstream _bendefence_session is kept only on the server. Access / refresh TTLs follow env ACCESS_TOKEN_TTL_MS and REFRESH_TOKEN_TTL_MS.
| Body Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | MyTaman email |
password | string | Yes | MyTaman password |
// 201 Created { "token": "c0a80164-1234-5678-9abc-def012345678", "refreshToken": "d1b91275-2345-6789-abcd-ef1234567890" }
| Status | Meaning |
|---|---|
201 | Session created; tokens returned |
400 | invalid_json or invalid_body (e.g. missing email/password) |
401 | login_failed — MyTaman rejected credentials |
500 | internal_error — e.g. could not persist session |
POST /api/auth/refresh
Exchange refreshToken for a new access token. Does not rotate the refresh token. No Authorization header.
| Body Field | Type | Required | Description |
|---|---|---|---|
refreshToken | string (UUID) | Yes | Value from login response |
// 200 OK { "token": "e2ca2386-3456-789a-bcde-f01234567890" }
| Status | Meaning |
|---|---|
200 | New Bearer token; TTL cache for that session id unchanged |
400 | invalid_json or invalid_body / missing refreshToken |
401 | unauthorized — detail: invalid_or_expired_refresh_token |
POST /api/auth/logout
Optional Authorization: Bearer <access token>. If the token resolves to a session, stored session data (access, refresh, cookie mapping) is removed and in-memory TTL cache for that session id is cleared. **Always 204 No Content** — including when the header is omitted, or the token is unknown/expired (idempotent). After access expiry, call refresh first, then logout with the new token to revoke the refresh token.
| Status | Meaning |
|---|---|
204 | Empty body; session deleted when a valid access token was sent |
GET /api/profile Bearer
Taman name and address parsed from MyTaman navigation bar. Cached 30 min.
TamanInfo
// 200 OK { "name": "Taman Bukit Indah", "address": "No 1, Jalan Bukit Indah 1/1" }
GET /api/billing/invoices Bearer
All invoice profiles from the dashboard dropdown. Cached 10 min.
InvoiceProfileOption[]
// 200 OK [ { "id": 9978, "name": "Monthly Maintenance", "selected": true }, { "id": 10234, "name": "Sinking Fund", "selected": false } ]
GET /api/billing/paid-invoices Bearer
Parsed rows from MyTaman Paid Invoices (/ra_dashboard/paid_invoices). Use page (1-based) and the same filter fields as the RA search form. Cached 10 min per page + filter set.
| Query param | Upstream |
|---|---|
page | Pagination |
paid_at_gteq, paid_at_lteq | Paid date range |
invoice_due_gteq, invoice_due_lteq | Due date range |
online_offline_search | Show All, Only Online, Only Offline |
PaidInvoicesPage
// 200 OK (abbrev.) { "invoices": [ { "id": 631729, "invoiceNumber": "318900631729", "userName": "JOHAN BIN AHMAD", "houseAddress": "741, Jalan Nada Alam 6/2", "amount": 70, "pdfPath": "/ra_dashboard/invoices/631729.pdf", "receiptPath": "/ra_dashboard/invoices/631729/receipt" } ], "page": 1, "lastPage": 108, "hasNext": true }
GET /api/billing/issued-invoices Bearer
Parsed rows from MyTaman Billing Invoices (/ra_dashboard/invoices). Use page (1-based) plus any of the search query params below (same semantics as the RA search form; sort maps to upstream q[s]). Cached 10 min per page + filter set.
| Query param | Upstream |
|---|---|
page | Pagination |
invoice_title_cont, invoice_number_cont | q[invoice_*] |
status_eq, status_not_eq | Status filters |
house_address_cont_all, house_address_eq | Address |
user_name_cont_all | Resident name |
invoice_profile_id_eq | Invoice profile id |
created_at_gteq, created_at_lteq | Issued date range |
paid_at_gteq, paid_at_lteq | Paid date range |
invoice_due_gteq, invoice_due_lteq | Due date range |
unpaid_invoice_pass_due_search | Unpaid past due (portal value) |
sort | e.g. invoice_number asc |
IssuedInvoicesPage
rel=next present// 200 OK (abbrev.) { "invoices": [ { "id": 654037, "invoiceNumber": "318900654037", "title": "Additional Access Card #4", "status": "Paid", "amount": 15, "editPath": "/ra_dashboard/invoices/654037/edit", "pdfPath": "/ra_dashboard/invoices/654037.pdf", "receiptPath": "/ra_dashboard/invoices/654037/receipt" } ], "page": 1, "lastPage": 618, "hasNext": true }
GET /api/billing/invoices/:id/stats Bearer
Billing statistics for one invoice profile. Cached 5 min. The :id is an invoice_profile_id from the list above.
CurrentInvoiceProfile
// 200 OK { "id": 9978, "title": "Monthly Maintenance", "frequency": "Monthly", "startDate": "2024-01-01", "totalIssued": { "count": 271, "amount": 21680.00 }, "totalCollected": { "count": 258, "amount": 19820.00 }, "totalOutstanding": { "count": 13, "amount": 1860.00 }, "paidHouses": { "paid": 279, "unpaid": 18 } }
GET /api/billing/dashboard Bearer
Full composite dashboard: taman info + all invoice profiles + current profile stats. Optional ?profile_id= to load a specific profile. Cached 5 min.
| Query Param | Type | Required | Description |
|---|---|---|---|
profile_id | integer | No | Load specific invoice profile |
// 200 OK { "taman": { "name": "...", "address": "..." }, "profiles": [ ... ], "current": { ... } }
Error handling
All errors return JSON with an error code and optional detail.
ErrorResponse
| Status | Error Code | When |
|---|---|---|
400 | invalid_json | Request body is not valid JSON |
400 | invalid_body | Missing required fields or invalid path param |
401 | unauthorized | Missing / invalid / expired Bearer token |
401 | login_failed | MyTaman rejected credentials or session expired |
404 | not_found | Unknown route |
502 | upstream_error | MyTaman returned non-200 |
500 | internal_error | Unexpected server error |
Caching strategy
Responses are cached in-memory per stable session id (not the Bearer string), so refreshing the access token does not flush the TTL cache.
| Resource | Cache Key | TTL |
|---|---|---|
| Taman profile | {sessionId}:profile | 30 min |
| Invoice profiles | {sessionId}:invoices | 10 min |
| Paid invoices | {sessionId}:paid-invoices[:filters]:{page} | 10 min |
| Issued invoices | {sessionId}:issued-invoices[:filters]:{page} | 10 min |
| Invoice stats | {sessionId}:invoice:{id} | 5 min |
| Full dashboard | {sessionId}:dashboard:{id|default} | 5 min |
POST /api/auth/logout) evicts all cached entries for that session. Cache entries also self-expire via lazy TTL check on read.
Architecture
How a protected read flows through the hub: Bearer access token resolves (server-side lookup + small in-memory cache) to a stable session id and MyTaman cookie; repositories consult the TTL cache before touching upstream.
Authorization: Bearerapplication/json · same shape as OpenAPI schemasNew MyTaman screens follow the same pattern: dedicated fetch module + parser + repository slice, sharing the global CacheStore and session cookie from login.
Release notes
API Changelog below describe changes that may affect your integration (HTTP surface, auth, JSON fields, caching visible to clients). Fetch GET /release-notes for this same content over the wire.
Downloads & tools
baseUrl, run Auth > Login — the test script saves token and refreshToken. Use Auth > Refresh when the access token expires.