MPLAY Connect API
Connect any platform (billing, ISP, WiFi, website, mobile app, POS, e-commerce or CRM) to MPLAY through one secure gateway with OAuth access tokens, scopes, webhooks and usage logs. MPLAY never receives or stores your payment credentials.
1. Getting Started
MPLAY Connect is the secure gateway that lets any authorised platform (Example Billing System, Example ISP, Example Website, a POS or a mobile app) work with MPLAY. You never touch MPLAY's database, video storage or payment gateways; you call the API and MPLAY stays the source of truth. Base URL: https://mplay.live/api/public/connect/v1 (also served at /api/connect/v1).
2. Create Developer Application
Sign in, open the Developer Center and apply. After an administrator approves you, create applications (one per system or environment). Each application has a client ID, a client secret (shown once), an environment, an integration type and a list of approved scopes. The integration type is only a label; permissions come from scopes.
3. Authentication
Server-to-server integrations use OAuth 2.0 Client Credentials. Exchange your client ID and secret for a short-lived access token, then send only the token on every call. Never put the secret in a browser, mobile app or URL.
4. OAuth Client Credentials
Request a token (form-encoded, JSON, or HTTP Basic auth are accepted). Ask for only the scopes you need; you'll never receive more than your approved scopes.
curl -X POST https://mplay.live/api/public/connect/v1/oauth/token \
-d grant_type=client_credentials \
-d client_id=mk_xxxxxxxx \
-d client_secret=$MPLAY_CLIENT_SECRET \
-d "scope=catalog.read packages.read"
{ "access_token": "mpat_…", "token_type": "Bearer", "expires_in": 3600,
"scope": "catalog.read packages.read", "environment": "sandbox" }5. Access Tokens
Tokens last 1 hour and are stored by MPLAY only as a hash. Cache the token and request a new one shortly before it expires or when you get TOKEN_EXPIRED. Rotating the secret, changing scopes, switching environment, suspending or revoking the app invalidates every token immediately.
6. API Scopes
Every endpoint needs a scope. Nothing is granted by default; an administrator approves scopes per application.
catalog.read movies.read series.read collections.read categories.read
packages.read packages.write customers.read
billing.transactions.write billing.transactions.read
purchases.read checkout.create entitlements.read entitlements.create watch.create
partners.read partner_links.create attribution.read commissions.read
settlements.read settlements.write captive_portal.create
webhooks.manage analytics.read7. API Requests
Send JSON over HTTPS with the bearer token. Bodies larger than 64 KB are rejected. Phone numbers are international digits without "+" (e.g. 255712345678).
curl https://mplay.live/api/public/connect/v1/movies?limit=20&page=1 \
-H "Authorization: Bearer $TOKEN"8. API Responses
{ "success": true, "data": { … }, "request_id": "req_8hd72k…", "api_version": "v1" }9. Request IDs
Every response carries X-MPLAY-Request-ID and X-MPLAY-API-Version. Quote the request ID when contacting support; it links to your entry in the API logs.
10. Catalog API
GET /content catalog.read ?q= &category_id= &page= &limit=
GET /content/{id} catalog.read (series include episodes)
GET /movies movies.read
GET /series series.read
GET /collections collections.read
GET /categories categories.read11. Packages API
GET /packages lists the MPLAY packages and your own packages (filters: kind, code, owner). POST /packages creates or updates one of your own packages. Package codes are unique across all platforms.
12. Billing API
POST /billing/purchases (billing.transactions.write) records a sale after your system has confirmed the payment. Send a unique transaction_id and idempotency_key; repeats return the original result and never create a second sale.
{ "transaction_id": "EX-10021", "idempotency_key": "EX-10021",
"phone": "255712345678", "package_code": "WIFI-MOVIE-3000",
"amount": 3000, "currency": "TZS", "payment_status": "successful",
"billing_partner": "example-billing", "isp_partner": "example-isp" }Only confirmed payments grant access. Anything else returns PAYMENT_NOT_VERIFIED and creates no entitlement.
13. Purchases API
GET /purchases and GET /purchases/{mplay_reference or your transaction_id} (purchases.read). You only ever see purchases your application created, in its own environment.
14. Entitlements API
GET /entitlements?phone=255712345678 (entitlements.read) returns active access for a customer your application has sold to. Other customers return an empty list.
15. Playback API
Playback stays inside MPLAY. Send customers to their MPLAY link (returned with each sale) or the captive portal session; video storage credentials are never shared. watch.create is reserved for approved player partners.
16. Partner API
Partner and campaign links use the existing MPLAY partner programme (/go/{code}, QR campaigns). Commission is always calculated by MPLAY's commission ledger. Scopes partners.read and partner_links.create are approved individually.
17. Attribution API
Pass partner_code (and campaign where supported) on sales and portal sessions; MPLAY attributes the purchase and commission. attribution.read is approved on request.
18. Settlement API
GET /settlements/outstanding settlements.read
GET /settlements/partners/{partner_code}/outstanding settlements.read
GET /settlements/invoices/{partner} settlements.read
POST /settlements/payments settlements.write
GET /settlements/payments/{reference} settlements.read
Money owed to MPLAY and commission owed to partners are kept separate and never offset.
Submitted payments are only applied after verification.19. Captive Portal API
POST /captive-portal/session (captive_portal.create) returns a short-lived, single-use redirect that carries partner and campaign attribution. Send only what's needed; a browser-supplied MAC or IP is never treated as identity.
20. Webhooks
POST /webhooks { "url": "https://example.com/mplay", "events": ["purchase.*", "settlement.paid"] }
GET /webhooks
DELETE /webhooks/{id}
POST /webhooks/testEvents: content.*, customer.*, purchase.*, entitlement.*, commission.*, settlement.*, watch.*. Each delivery body is { id, type, api_version, created_at, reference, data }. The signing secret is returned once at creation.
21. Webhook Security
Check the signature, reject timestamps older than 5 minutes, and store X-MPLAY-Event-ID so duplicates are ignored. Reply 2xx within 8 seconds. Failed deliveries retry with exponential backoff up to 8 times, then show as failed in your dashboard.
// Node.js
import crypto from "node:crypto";
const ts = req.headers["x-mplay-timestamp"];
const sig = req.headers["x-mplay-signature"].split("v1=")[1];
const expected = crypto.createHmac("sha256", process.env.MPLAY_WEBHOOK_SECRET)
.update(`${ts}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
if (!fresh || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return res.status(401).end();22. Rate Limits
Limits are set per application (and optionally per endpoint). Responses include X-RateLimit-Limit and X-RateLimit-Reset; if you go over, you get 429 RATE_LIMITED with Retry-After.
23. Errors
{ "success": false,
"error": { "code": "INSUFFICIENT_SCOPE", "message": "…" },
"request_id": "req_…" }Codes: UNAUTHORIZED, INVALID_CLIENT, INVALID_TOKEN, TOKEN_EXPIRED, INSUFFICIENT_SCOPE, APPLICATION_SUSPENDED, RESOURCE_NOT_FOUND, VALIDATION_ERROR, DUPLICATE_REQUEST, PAYMENT_NOT_VERIFIED, RATE_LIMITED, WEBHOOK_ERROR, PAYLOAD_TOO_LARGE, INTERNAL_ERROR. Older endpoints keep their original body format for compatibility.
24. Sandbox
New applications start in sandbox. Sandbox sales are processed and visible to you but create no real commission or settlement debt, and sandbox tokens can't read production purchases.
25. Production
Switch an application to live from the Developer Center once testing passes (live mode requires an approved application). Tokens issued in sandbox stop working after the switch; request a new one.
26. API Logs
The Developer Center shows 7-day call and error counts and your latest calls (endpoint, status, time, request ID). Secrets, tokens and payment details are never logged.
27. Security Best Practices
Keep secrets on your server, rotate them regularly and immediately if exposed, request least-privilege scopes, allow-list your server IPs, verify every webhook, use idempotency keys for money operations, and never send customer PINs or passwords to MPLAY.
28. Migrating from signed requests
Existing integrations that sign each request with X-MPLAY-Key-Id / X-MPLAY-Signature keep working unchanged. To migrate, use the same key ID as client_id and your secret as client_secret to get a token, then replace the signature headers with Authorization: Bearer. Signed requests will only be retired through a versioned, announced process.
Signed requests and billing details
Detailed payloads for billing, packages, settlements and the captive portal. Every endpoint below also accepts an OAuth bearer token instead of the signature headers.
1. Credentials
Sign in and apply on the developer access page. Once an MPLAY administrator approves your application you receive a Key ID and a Secret. The secret is shown once. Every connection starts in test mode, where sales are processed but create no real commission.
2. Signing a request
Every call carries three headers. The signature is HMAC-SHA256 over the timestamp, a dot, and the exact raw body (empty string for GET requests). Requests older than the configured window are rejected.
X-MPLAY-Key-Id: <your key id>
X-MPLAY-Timestamp: <unix seconds>
X-MPLAY-Signature: hex( hmac_sha256(secret, timestamp + "." + rawBody) )
Content-Type: application/json3. Discover and create packages
GET /api/public/connect/v1/packages returns the official MPLAY packages plus the packages your system created (owner: mplay or self). Filters: ?kind=internet|content|bundle, ?code=CODE, ?owner=mine|mplay|all, ?include_inactive=1. Sign over an empty body.
POST /api/public/connect/v1/packages creates or updates (by code) one of your own packages, active immediately. Fields: code, name, kind (internet | content | bundle), price_tzs, validity_hours, internet_value_tzs, entertainment_value_tzs, entertainment_type (movie | series), selection_quantity, series_scope, data_limit_mb, speed_mbps, device_limit, status, attributes (up to 30 extra key/values). Codes owned by MPLAY or another partner return 409 code_taken. Sales using your package code must come from your own key. Internet-only packages do not grant movie passes.
4. Report a confirmed sale
POST /api/public/connect/v1/billing/purchases. Only confirmed payments are accepted. Retries are safe: transaction_id is the idempotency key, and a repeat returns the original result.
{
"transaction_id": "NB-2026-0001",
"phone": "255712345678",
"package_code": "INTERNET_MOVIE_3000",
"amount": 3000,
"currency": "TZS",
"billing_partner": "nanobill",
"isp_partner": "mkinguzinet",
"partner_code": "testpartner",
"payment_status": "successful",
"paid_at": "2026-01-05T10:15:00Z"
}201 Created
{
"success": true,
"status": "processed",
"mplay_reference": "cnx_...",
"entitlement": {
"type": "MOVIE_PASS",
"allowed_selections": 1,
"used_selections": 0,
"remaining_selections": 1
},
"commission": { "rate": 20, "amount": 300, "currency": "TZS", "sandbox": false }
}5. Errors
401 invalid_signature / stale_timestamp / invalid_key— check the secret and clock.403 ip_not_allowed— your server IP is not on the allowlist.422 unknown_package / amount_mismatch / payment_not_confirmed / invalid_phone429 rate_limited— slow down and retry.503 integration_disabled— the programme is switched off.
Every response carries an x-request-id header. Quote it when contacting support.
6. Check a sale or a pass
GET /api/public/connect/v1/status?transaction_id=NB-2026-0001 returns the sale we recorded and the passes it created. GET /api/public/connect/v1/status?entitlement=ent_… returns how many picks are left on a pass. Called with no query it is a signed health check.
7. What the customer sees
The customer signs in with the same phone number and picks their titles from the normal catalogue. Watching, downloads and progress work exactly as with a direct purchase.
Outstanding balances & payments
When a bundle is sold, the internet provider collects the customer's money. Out of the movie share of that price the provider keeps its agreed commission and the remainder becomes a balance the provider owes MPLAY. The billing partner controls that invoice: it issues it and collects the payment on our behalf. A balance stays in accruing until it reaches the provider's collection threshold, then becomes payable. This is the opposite direction to partner commission, which MPLAY pays out to you and is never netted off here.
Signing is identical to the rest of the Connect API. Your connection additionally needs the settlements.read and/or settlements.write scope, and may be limited to a list of partner codes.
Payload changes
Phone numbers are international format without the plus sign (255712345678, 254712345678). A leading + and Tanzanian local formats such as 0712345678 are still accepted, and responses always return the number as international digits with no plus. Billing purchases now require two partner identifiers: billing_partner, the organisation that collected the money and therefore settles with MPLAY, and isp_partner, the internet provider whose network carried the customer (used for reporting). partner_code remains the optional marketing partner who earns commission. A sale missing either identifier is rejected with 422 missing_partner_identifiers. Administrators can switch this requirement off in Admin → Connect → Settings ("Require billing + ISP partner") for older integrations.
POST /api/public/connect/v1/billing/purchases
{
"transaction_id": "TXN-1001",
"phone": "255712345678",
"package_code": "INTERNET_MOVIE_3000",
"amount": 3000,
"currency": "TZS",
"payment_status": "successful",
"billing_partner": "nanobill", // required — controls and collects the provider invoice
"isp_partner": "mkinguzinet", // required — internet provider; keeps its share, owes MPLAY the rest
"partner_code": "testpartner" // optional — earns commission
}422 Unprocessable Entity
{
"success": false,
"error": "missing_partner_identifiers",
"message": "Both billing_partner and isp_partner are required.",
"details": [{ "field": "isp_partner", "issue": "required" }]
}Endpoints
GET /api/public/connect/v1/settlements/outstanding
GET /api/public/connect/v1/settlements/outstanding/{partner_code}
GET /api/public/connect/v1/settlements/outstanding/isp/{isp_partner_code}
GET /api/public/connect/v1/settlements/invoices/{partner_code}?page=1&limit=20
POST /api/public/connect/v1/settlements/payments
GET /api/public/connect/v1/settlements/payments/{payment_reference}GET /settlements/outstanding
{
"success": true,
"currency": "TZS",
"total_outstanding": 1250000,
"partner_count": 3,
"isp_count": 2,
"partners": [ { "partner_code": "nanobill", "outstanding_amount": 750000 } ],
"isp_partners": [
{ "isp_partner_code": "mkinguzinet", "isp_partner_name": "MkinguziNet",
"outstanding_amount": 900000, "partner_count": 2 }
]
}curl -X POST https://mplay.live/api/public/connect/v1/settlements/payments \
-H "X-MPLAY-Key-Id: $KEY_ID" \
-H "X-MPLAY-Timestamp: $TS" \
-H "X-MPLAY-Signature: $SIG" \
-H "Content-Type: application/json" \
-d '{
"partner_code": "nanobill",
"invoice_reference": "INV-20260210-001",
"amount": 150000,
"currency": "TZS",
"payment_reference": "MPESA-QW12345",
"idempotency_key": "pay-2026-02-10-01"
}'201 Created
{
"success": true,
"payment_reference": "MPESA-QW12345",
"status": "verified",
"amount_paid": 150000,
"previous_outstanding": 150000,
"remaining_outstanding": 0,
"settlement_status": "paid",
"invoice_reference": "INV-20260210-001",
"currency": "TZS"
}Verification, full payment and idempotency
- MPLAY matches your payment reference against its own confirmed money trail before reducing a balance.
- A reference we cannot match is stored as
pending_verificationand changes nothing until finance approves it. - Partial payments are NOT accepted:
amountmust equal the invoice’s full outstanding balance, otherwise the call returns422 PARTIAL_PAYMENT_NOT_ALLOWED. - A repeated
payment_referencereturns409 DUPLICATE_PAYMENT; a repeatedidempotency_keywith the same body replays the original result. - Reversals and refunds raise the balance again and are written to the audit trail.
Webhooks
Set a webhook address and secret on your connection to receive settlement events. Signature is HMAC-SHA256 over timestamp + "." + body, sent as X-MPLAY-Signature. Retries use exponential backoff, so treat event_id as an idempotency key.
Events: settlement.created, settlement.updated, settlement.payment_received,
settlement.partially_paid, settlement.paid, settlement.overdue,
settlement.payment_reversed
{
"event": "settlement.paid",
"event_id": "evt_9f2c1b7a4d33",
"occurred_at": "2026-02-10T09:14:00Z",
"partner_code": "nanobill",
"invoice_reference": "INV-20260210-001",
"currency": "TZS",
"remaining_outstanding": 0,
"status": "paid"
}Errors
401 UNAUTHORIZED bad signature, key or timestamp
403 INSUFFICIENT_SCOPE missing settlements scope, or partner not allowed
404 INVALID_PARTNER unknown partner code
404 INVOICE_NOT_FOUND unknown or cancelled invoice
404 PAYMENT_NOT_FOUND unknown payment reference
409 DUPLICATE_PAYMENT reference already recorded
409 IDEMPOTENCY_CONFLICT key reused with a different payload
409 ALREADY_PAID nothing outstanding on this invoice
422 INVALID_AMOUNT zero, negative or more than outstanding
422 INVALID_CURRENCY currency does not match the invoice
429 RATE_LIMITED slow down and retryReconciliation tip: read /settlements/outstanding/{partner_code} after each payment and compare it with your own ledger; the value is always recomputed from invoices, adjustments and verified payments.
MikroTik / WiFi hotspot
A hotspot login page can show MPLAY titles before the visitor is online, and can hand the visitor to MPLAY with partner or campaign tracking attached. Preview never unlocks anything: paid titles still go through the normal sign-in, payment and entitlement rules.
Architecture
MikroTik hotspot -> preview API -> movie cards -> customer taps a title
-> MPLAY -> login/register -> payment -> entitlement -> watch
MikroTik -> partner/campaign link -> MPLAY -> attribution -> purchase -> commission ledgerPreview API — PUBLIC
GET /api/connect/v1/captive-portal/movies (mirrored at /api/public/connect/v1/captive-portal/movies). No key required, cached for two minutes, rate limited per IP. Default limit 10, maximum 50.
GET /api/connect/v1/captive-portal/movies?page=1&limit=10&type=movie&category=action&featured=1
{
"success": true,
"data": [
{
"id": "PUBLIC_CONTENT_ID",
"title": "MUTINY",
"type": "movie",
"poster_url": "https://...",
"price": 1000,
"currency": "TZS",
"is_free": false,
"episodes": null,
"year": 2024,
"preview_url": "https://mplay.live/title/PUBLIC_CONTENT_ID"
}
],
"meta": { "page": 1, "limit": 10, "total": 50 }
}Movie card example
<a class="movie-card" href="PREVIEW_URL">
<img src="POSTER_URL" alt="MOVIE TITLE">
<div class="movie-card-info"><strong>MOVIE TITLE</strong></div>
</a>Partner & campaign tracking — PUBLIC
/connect/<PARTNER_CODE> and /c/<CAMPAIGN_CODE> are aliases of the existing entry points /a/<CODE> and /api/public/go/<CODE>. Attribution survives click, landing, sign-up, login, payment and purchase, and feeds the one existing commission ledger.
Session API — SERVER-TO-SERVER
POST /api/connect/v1/captive-portal/session, signed with the same three headers as every other Connect call. Returns a short-lived, single-use, non-guessable redirect. IP and MAC may be sent for your own correlation but are never stored and are never treated as proof of identity.
{
"partner_code": "PARTNER001",
"campaign_code": "WIFI001",
"router_ref": "ROUTER001",
"session_ref": "SESSION123",
"return_url": "https://mplay.live/"
}201 Created
{
"success": true,
"session_token": "...",
"expires_at": "2026-01-05T10:30:00.000Z",
"redirect_url": "https://mplay.live/connect/session/..."
}Walled garden configuration
Keep it to three lines. MPLAY itself, plus one wildcard for every poster and image served by the CDN — nothing else is needed.
/ip hotspot walled-garden
add dst-host=mplay.live
add dst-host=www.mplay.live
add dst-host=*.b-cdn.netDo not add individual pull-zone hostnames, media aliases or extra CDN domains: *.b-cdn.net covers all MPLAY artwork. Only add your own billing platform host if the portal page calls it directly.
Security, limits and errors
- No Bunny credentials, playback URLs, payment secrets or customer data in any portal response.
- Session tokens are random, signed, single-use, short-lived and replay-protected.
- Rate limit and allowed portal web addresses are set by an administrator.
503 captive_portal_disabled,429 rate_limited,422 invalid_request,401 invalid_signature / stale_timestamp,403 attribution_disabled.
Troubleshooting
- Blank cards: check the walled garden allows MPLAY and the poster hostname.
- Empty list: the portal API may be switched off, or “featured only” may be enabled.
- Redirect lands without tracking: the session link is single-use — mint a new one per visitor.
- 401 responses: check the shared secret and the router clock.