{"openapi":"3.1.0","info":{"title":"FinHub API","version":"1.0.0","description":"REST API for FinHub billing and subscription management. Two access levels: Company (full management) and Client (self-service via /my/ namespace)."},"servers":[{"url":"/api/v1","description":"API v1"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Obtain a token via POST /api/v1/login with { email, password }. Token expires in 24 hours."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}}},"LoginRequest":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"}}},"LoginResponse":{"type":"object","properties":{"type":{"type":"string","example":"bearer"},"token":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}},"Client":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"company_id":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Product":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"company_id":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"BillingModel":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"billing_type_id":{"type":"integer"},"price":{"type":"number"},"currency":{"type":"string"},"description":{"type":"string"},"company_id":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Subscription":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"external_subscription_id":{"type":"string"},"client_id":{"type":"integer"},"product_id":{"type":"integer"},"billing_model_id":{"type":"integer"},"company_id":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"BillingRecord":{"type":"object","properties":{"id":{"type":"integer"},"subscription_id":{"type":"integer"},"billedUnits":{"type":"number"},"totalCharges":{"type":"string"},"billingStartAt":{"type":"string","format":"date-time"},"billingEndAt":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"FormConfig":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"config":{"type":"object"},"client_id":{"type":"integer"},"company_id":{"type":"integer"},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"UsageEvent":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"subscription_id":{"type":"string","description":"Composite key: companyId:externalSubId"},"timestamp":{"type":"integer","description":"Unix timestamp in milliseconds"},"usage":{"type":"number"},"metadata":{"type":"object","additionalProperties":true},"created_at":{"type":"string","format":"date-time"}}},"UsageSearchRequest":{"type":"object","properties":{"client_ids":{"type":"array","items":{"type":"integer"}},"subscription_ids":{"type":"array","items":{"type":"integer"}},"external_subscription_ids":{"type":"array","items":{"type":"string"}},"product_ids":{"type":"array","items":{"type":"integer"}},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"metadata_search":{"type":"string"},"start_time":{"type":"integer","description":"Absolute start (ms)"},"end_time":{"type":"integer","description":"Absolute end (ms)"},"relative":{"type":"string","enum":["last_1h","last_24h","last_7d","last_30d","last_90d","this_month","last_month"],"description":"Relative time period (overrides start_time/end_time)"},"min_usage":{"type":"number"},"max_usage":{"type":"number"},"sort_by":{"type":"string","enum":["timestamp","usage"],"default":"timestamp"},"sort_order":{"type":"string","enum":["asc","desc"],"default":"desc"},"page":{"type":"integer","default":1},"limit":{"type":"integer","default":50,"maximum":200},"aggregate":{"type":"boolean","default":false},"group_by":{"type":"string","enum":["subscription","client","product","day","hour"]}}},"UsageAggregation":{"type":"object","properties":{"group_key":{"type":"string"},"total_usage":{"type":"number"},"event_count":{"type":"integer"}}},"CreditBalance":{"type":"object","properties":{"allocated":{"type":"integer","description":"Credits allocated this month"},"used":{"type":"integer","description":"Base credits consumed"},"remaining":{"type":"integer","description":"Base credits remaining"},"overageUsed":{"type":"integer","description":"Overage credits consumed"},"overageRate":{"type":"number","nullable":true,"description":"Per-credit overage rate"},"creditConsumption":{"type":"string","enum":["per_api_call","per_unit"],"description":"How credits are consumed"},"planName":{"type":"string"},"currency":{"type":"string","description":"Currency code (e.g. MYR, USD)"}}}}},"security":[{"bearerAuth":[]}],"paths":{"/login":{"post":{"tags":["Auth"],"summary":"Get JWT token","description":"Authenticate with email and password to receive a JWT bearer token valid for 24 hours.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"JWT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"401":{"description":"Invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/clients":{"get":{"tags":["Company - Clients"],"summary":"List all clients","responses":{"200":{"description":"Client list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Client"}}}}}}}}},"post":{"tags":["Company - Clients"],"summary":"Create a client","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}},"responses":{"201":{"description":"Created client","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Client"}}}}}}}}},"/clients/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["Company - Clients"],"summary":"Get client details","responses":{"200":{"description":"Client with subscriptions","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Client"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Company - Clients"],"summary":"Update a client","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated client","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Client"}}}}}}}},"delete":{"tags":["Company - Clients"],"summary":"Delete a client","description":"Fails if the client has active subscriptions.","responses":{"200":{"description":"Client deleted"},"409":{"description":"Client has active subscriptions"}}}},"/products":{"get":{"tags":["Company - Products"],"summary":"List all products","responses":{"200":{"description":"Product list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Product"}}}}}}}}},"post":{"tags":["Company - Products"],"summary":"Create a product","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}},"responses":{"201":{"description":"Created product"}}}},"/products/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["Company - Products"],"summary":"Get product details","responses":{"200":{"description":"Product details"}}},"put":{"tags":["Company - Products"],"summary":"Update a product","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated product"}}},"delete":{"tags":["Company - Products"],"summary":"Delete a product","responses":{"200":{"description":"Product deleted"}}}},"/billing-models":{"get":{"tags":["Company - Billing Models"],"summary":"List all billing models","responses":{"200":{"description":"Billing model list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingModel"}}}}}}}}},"post":{"tags":["Company - Billing Models"],"summary":"Create a billing model","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["billing_type_id","name","price","currency","description"],"properties":{"billing_type_id":{"type":"integer"},"name":{"type":"string"},"price":{"type":"number"},"currency":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"201":{"description":"Created billing model"}}}},"/billing-models/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["Company - Billing Models"],"summary":"Get billing model details with usage tiers","responses":{"200":{"description":"Billing model with tiers"}}}},"/subscriptions":{"get":{"tags":["Company - Subscriptions"],"summary":"List all subscriptions","responses":{"200":{"description":"Subscription list with related client, product, billing model","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}}}}}}}}},"post":{"tags":["Company - Subscriptions"],"summary":"Create a subscription","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","external_subscription_id","product_id","client_id","billing_model_id"],"properties":{"name":{"type":"string"},"external_subscription_id":{"type":"string"},"product_id":{"type":"integer"},"client_id":{"type":"integer"},"billing_model_id":{"type":"integer"}}}}}},"responses":{"201":{"description":"Created subscription"}}}},"/subscriptions/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["Company - Subscriptions"],"summary":"Get subscription details","responses":{"200":{"description":"Subscription with relations"}}}},"/billing-records":{"get":{"tags":["Company - Billing Records"],"summary":"List billing records (paginated)","parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Paginated billing records"}}}},"/billing-records/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["Company - Billing Records"],"summary":"Get billing record details","responses":{"200":{"description":"Billing record with subscription"}}}},"/usage/search":{"post":{"tags":["Company - Usage"],"summary":"Search usage events","description":"Search and filter usage events across all company subscriptions. Supports filtering by client, subscription, product, metadata, date range, and usage amount. Returns paginated events or aggregated totals grouped by various dimensions.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageSearchRequest"}}}},"responses":{"200":{"description":"Search results (paginated events or aggregated data)","content":{"application/json":{"schema":{"oneOf":[{"type":"object","description":"Paginated events (aggregate=false)","properties":{"meta":{"type":"object"},"data":{"type":"array","items":{"$ref":"#/components/schemas/UsageEvent"}}}},{"type":"object","description":"Aggregated results (aggregate=true)","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/UsageAggregation"}}}}]}}}},"403":{"description":"Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error"}}}},"/subscriptions/{id}/credits":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["Company - Credits"],"summary":"Get credit balance for a subscription","description":"Returns current month credit allocation, usage, remaining balance, and overage for credit-based subscriptions.","responses":{"200":{"description":"Credit balance","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreditBalance"}}}}}},"400":{"description":"Subscription is not credit-based"}}}},"/subscriptions/{id}/upgrade":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"post":{"tags":["Company - Credits"],"summary":"Upgrade credit plan (immediate)","description":"Immediately upgrades to a higher credit plan. Prorated additional credits are added for the remainder of the month.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["new_billing_model_id"],"properties":{"new_billing_model_id":{"type":"integer"}}}}}},"responses":{"200":{"description":"Plan upgraded"},"422":{"description":"New plan must have more credits"}}}},"/subscriptions/{id}/downgrade":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"post":{"tags":["Company - Credits"],"summary":"Downgrade credit plan (next month)","description":"Queues a downgrade to a lower credit plan. Takes effect at the start of the next billing month.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["new_billing_model_id"],"properties":{"new_billing_model_id":{"type":"integer"}}}}}},"responses":{"200":{"description":"Downgrade scheduled"},"422":{"description":"New plan must have fewer credits"}}}},"/subscriptions/{id}/billing-model-changes":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"post":{"tags":["Company - Billing Model Changes"],"summary":"Schedule or apply a billing model change","description":"Change the billing model on a subscription with an effective date. If effective_at is now or in the past, applies immediately (retroactive). If in the future, schedules for later.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["new_billing_model_id","effective_at"],"properties":{"new_billing_model_id":{"type":"integer"},"effective_at":{"type":"string","format":"date-time","description":"ISO 8601 date-time for when the change takes effect"}}}}}},"responses":{"200":{"description":"Change applied or scheduled"},"422":{"description":"Validation error (same model, duplicate schedule, etc.)"}}},"get":{"tags":["Company - Billing Model Changes"],"summary":"List billing model changes for a subscription","description":"Returns all billing model changes (scheduled, applied, cancelled) ordered by effective date.","responses":{"200":{"description":"List of billing model changes"}}}},"/subscriptions/{id}/billing-model-changes/{changeId}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"changeId","in":"path","required":true,"schema":{"type":"integer"}}],"delete":{"tags":["Company - Billing Model Changes"],"summary":"Cancel a scheduled billing model change","description":"Only scheduled (not yet applied) changes can be cancelled.","responses":{"200":{"description":"Change cancelled"},"404":{"description":"Scheduled change not found"}}}},"/my/subscriptions":{"get":{"tags":["Client - Subscriptions"],"summary":"List my subscriptions","description":"Returns subscriptions belonging to the authenticated client.","responses":{"200":{"description":"Client subscription list"}}}},"/my/subscriptions/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["Client - Subscriptions"],"summary":"Get my subscription details","responses":{"200":{"description":"Subscription details"}}}},"/my/billing-records":{"get":{"tags":["Client - Billing Records"],"summary":"List my billing records (paginated)","parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Paginated billing records"}}}},"/my/billing-records/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["Client - Billing Records"],"summary":"Get my billing record details","responses":{"200":{"description":"Billing record details"}}}},"/my/forms":{"get":{"tags":["Client - Forms"],"summary":"List my form configs","responses":{"200":{"description":"Form config list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FormConfig"}}}}}}}}},"post":{"tags":["Client - Forms"],"summary":"Create a form config","description":"Provide a structured config object. The config is validated against the FinSys schema.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","config"],"properties":{"name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":1000},"config":{"type":"object","required":["title","fields"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"fields":{"type":"array","items":{"type":"object","required":["name","label","type"],"properties":{"name":{"type":"string"},"label":{"type":"string"},"type":{"type":"string","enum":["text","number","email","tel","select","textarea","date","checkbox"]},"required":{"type":"boolean"},"placeholder":{"type":"string"},"options":{"type":"array","items":{"type":"string"}}}}}}}}}}}},"responses":{"201":{"description":"Created form config"},"422":{"description":"Validation failed"}}}},"/my/forms/validate":{"post":{"tags":["Client - Forms"],"summary":"Validate a form config without saving","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"config":{"type":"object"}}}}}},"responses":{"200":{"description":"Validation result","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean"},"errors":{"type":"array","items":{"type":"string"}}}}}}}}}},"/my/forms/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"get":{"tags":["Client - Forms"],"summary":"Get my form config details","responses":{"200":{"description":"Form config details"}}},"put":{"tags":["Client - Forms"],"summary":"Update my form config","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","config"],"properties":{"name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":1000},"config":{"type":"object","description":"Form config object (same structure as create)","required":["title","fields"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"fields":{"type":"array","items":{"type":"object","required":["name","label","type"],"properties":{"name":{"type":"string"},"label":{"type":"string"},"type":{"type":"string","enum":["text","number","email","tel","select","textarea","date","checkbox"]},"required":{"type":"boolean"},"placeholder":{"type":"string"},"options":{"type":"array","items":{"type":"string"}}}}}}},"is_active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated form config"},"422":{"description":"Validation failed"}}},"delete":{"tags":["Client - Forms"],"summary":"Delete my form config","responses":{"200":{"description":"Form config deleted"}}}},"/emandate/products":{"get":{"tags":["Company - E-Mandate"],"summary":"List e-mandate products","responses":{"200":{"description":"List of products"}}},"post":{"tags":["Company - E-Mandate"],"summary":"Create e-mandate product","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["product_code","product_name","max_amount","frequency"],"properties":{"product_code":{"type":"string"},"product_name":{"type":"string"},"max_amount":{"type":"string"},"max_no_debit":{"type":"string"},"frequency":{"type":"string","enum":["Daily","Weekly","Monthly","Yearly"]}}}}}},"responses":{"201":{"description":"Product created"},"422":{"description":"Validation failed"}}}},"/emandate/products/{id}":{"get":{"tags":["Company - E-Mandate"],"summary":"Get e-mandate product","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Product details"}}},"put":{"tags":["Company - E-Mandate"],"summary":"Update e-mandate product","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Product updated"}}},"delete":{"tags":["Company - E-Mandate"],"summary":"Delete e-mandate product","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Product deleted"}}}},"/emandate/mandates":{"get":{"tags":["Company - E-Mandate"],"summary":"List e-mandate mandates (applications)","parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Paginated list of mandates"}}},"post":{"tags":["Company - E-Mandate"],"summary":"Create mandate for a customer (auto-creates customer account if new)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["customer_name","customer_email"],"properties":{"customer_name":{"type":"string"},"customer_email":{"type":"string","format":"email"},"customer_phone":{"type":"string"},"customer_ic_number":{"type":"string"},"emandate_product_id":{"type":"integer"},"fpx_txn_amount":{"type":"string"},"frequency_mode":{"type":"string","enum":["DL","WK","MT","YR"]},"frequency":{"type":"string","enum":["Daily","Weekly","Monthly","Yearly"]}}}}}},"responses":{"201":{"description":"Mandate created, customer onboarded"},"422":{"description":"Validation failed"}}}},"/emandate/mandates/{id}":{"get":{"tags":["Company - E-Mandate"],"summary":"Get mandate details with payment history","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Mandate details with payments"}}}},"/emandate/customers":{"get":{"tags":["Company - E-Mandate"],"summary":"List customers onboarded through e-mandate","parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":20}},{"name":"search","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of customers"}}}},"/emandate/banks":{"get":{"tags":["Company - E-Mandate"],"summary":"List available FPX banks","responses":{"200":{"description":"List of banks"}}}},"/emandate/config":{"get":{"tags":["Company - E-Mandate"],"summary":"Get company FPX merchant config","responses":{"200":{"description":"FPX config details"}}}}},"tags":[{"name":"Auth","description":"Authentication"},{"name":"Company - Clients","description":"Client management (company users)"},{"name":"Company - Products","description":"Product management (company users)"},{"name":"Company - Billing Models","description":"Billing model management (company users)"},{"name":"Company - Subscriptions","description":"Subscription management (company users)"},{"name":"Company - Billing Records","description":"Billing record viewing (company users)"},{"name":"Company - Usage","description":"Usage event search and analytics (company users)"},{"name":"Company - Credits","description":"Credit subscription management (company users)"},{"name":"Company - Billing Model Changes","description":"Schedule, list, and cancel billing model changes with effective dates"},{"name":"Client - Subscriptions","description":"View own subscriptions (client users)"},{"name":"Client - Billing Records","description":"View own billing records (client users)"},{"name":"Client - Forms","description":"Form config management (client users)"},{"name":"Company - E-Mandate","description":"E-Mandate (FinCollect) management — products, mandates, customers, banks, FPX config"}]}