Documentation

Everything you need to build with Porta

Guides for getting started on the platform, plus API reference for developers integrating with Porta.

Overview

Porta brings management & Lite Web, Lite Booking, Marketplace, Lite Shop, Boost, Utilities, Finance and Rewards into a single account. This guide covers the basics of getting set up, followed by a reference for developers building on top of the Porta API.

Creating an Account

Sign up with your email or phone number from the Porta app or at portahq.net/app. You'll be asked to verify your email and complete identity verification (KYC) before you can send payments, host paid events, or list products, see our KYC Policy for what's required.

Setting Up Lite Web

From your dashboard, choose Lite Web → Create Page. Add your business name, logo, bio, and links to your social profiles. You can attach a booking calendar, product listings, or a simple contact form, enable only what you need.

Your Lite Web URL is reserved once claimed. Choose a handle you'll want to keep, it can be changed later from Settings, but existing links won't redirect automatically.

Funding Your Wallet

Add funds via bank transfer or card from Finance → Wallet → Add Money. Funds are typically available within a few minutes for cards and up to a few hours for bank transfers, depending on your bank.

Lite Booking & Ticketing

To host an event, go to Lite Booking → Create Event, set your ticket tiers and capacity, and publish. Attendees receive a digital ticket with a QR code you can scan at the door using the Porta organizer app.

Marketplace & Lite Shop

Sellers list products under Marketplace → My Listings. Orders, shipping status and buyer messages are managed from the same screen. Lite Shop purchases are handled the same way from the buyer side, with delivery tracking for supported categories.

Boost

Choose a platform (Instagram, TikTok, YouTube, and more), select a package, and provide the content link. Delivery times vary by package and are shown before you confirm.

Utilities

Pay airtime, data, TV subscriptions and electricity bills from Utilities. Enter the recipient details, confirm the amount, and pay from your wallet or card, no markup on face-value bills.

API Overview

The Porta API lets you reach your own data on Porta, your LiteShop orders, wallet balance, Lite Fund campaigns, System invoices, Lite Gigs, LiteBookings events and P2P orders, from your own website or backend, using a single set of credentials that covers every service. It's a REST API: JSON in, JSON out, one base URL, plain HTTP status codes.

// Base URL for every endpoint below https://portahq.netlify.app/api/v1

AML/fraud detection and KYC/verification are internal risk-control systems and are not exposed through this API, in any scope, under any plan. Everything else Porta does is either live below or listed as coming soon.

Getting Developer Access

There are two independent ways to unlock API access. Either one is enough on its own, they don't stack into a discount on each other:

  • Premium Porta Plan and up - API access is included automatically once you're on Premium or Premium Plus. See Pricing.
  • Developer upgrade - a one-time ₦50,000 payment from Reward Centre → Developer, available regardless of your current plan. This is a separate, standalone upgrade: it's independent of Mini Affiliate/Affiliate status, and being Mini Affiliate or Affiliate never discounts it, it's always the full ₦20,000.

Once either path applies to your account, generate your credentials from Management → Developer → API Keys in the app. That screen is also where you turn individual services on or off, regenerate, stop, or permanently delete your credentials, all of it is covered under Managing Credentials below.

Authentication

Porta issues one credential set per developer account: an API key, a secret, and a passkey. Unlike a typical single-token API, all three are required on every request, sent as three separate headers. A leaked key alone (for example, accidentally logged by your own server) is not enough on its own to authenticate:

Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxx X-Porta-Secret: sk_live_xxxxxxxxxxxxxxxxxxxxxxxx X-Porta-Passkey: pk_pass_xxxxxxxxxxxxxxxx

All three are shown to you exactly once, at the moment you create or regenerate your credentials. Porta stores only a hash of each, never the original values, the same way your account password is handled, so if you lose them, the only recovery path is to regenerate a fresh set (see Managing Credentials).

Example: authenticating a request

# curl curl https://portahq.netlify.app/api/v1/wallet/balance \ -H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Porta-Secret: sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "X-Porta-Passkey: pk_pass_xxxxxxxxxxxxxxxx"
// JavaScript (fetch) const res = await fetch('https://portahq.netlify.app/api/v1/wallet/balance', { headers: { 'Authorization': 'Bearer ' + PORTA_API_KEY, 'X-Porta-Secret': PORTA_SECRET, 'X-Porta-Passkey': PORTA_PASSKEY, } }); const data = await res.json(); // { success: true, balanceNgn: 45000 }
// Node.js const res = await fetch('https://portahq.netlify.app/api/v1/liteshop/orders', { method: 'GET', headers: { Authorization: `Bearer ${process.env.PORTA_API_KEY}`, 'X-Porta-Secret': process.env.PORTA_SECRET, 'X-Porta-Passkey': process.env.PORTA_PASSKEY, }, }); if (!res.ok) { throw new Error(`Porta API error: ${res.status}`); } const { orders } = await res.json();

Keep all three values server-side. Never ship them in client-side JavaScript, a mobile app bundle, or a public repo, treat them like a database password.

Scopes & Services

Your credential set doesn't call everything by default, you choose which services it's allowed to reach from Management → Developer → API Keys, where each service has its own on/off toggle. A request to a service you haven't enabled returns 403, even with a valid key/secret/passkey. Turn on only what you actually use.

ScopeCoversStatus
liteshopYour stores' orders, products and inventoryLive
walletYour Porta wallet balance and transaction historyLive
litefundYour fundraising campaigns and donations receivedLive
systemYour System invoices and their payment statusLive
gigsYour Lite Gigs listings and ordersLive
bookingsYour LiteBookings events and their bookingsLive
p2pYour P2P ads and orders (trade execution only)Live
litewebYour LiteWeb page content and site statsLive
liteformYour forms and their submission countsLive
boostYour Boost campaigns and delivery statusLive
accountsYour purchased Accounts S1/S2 inventoryLive
numbersYour purchased virtual numbersLive
tellySend a message to Ask Telly and get a replyLive
requestsYour posted requests and offers receivedLive
paymentsYour fiat and crypto payment/checkout links and their statusLive
pngnYour pNGN balance and transfer historyLive
convertLive conversion rate quotes between supported currencies/assetsLive

Every scope in the table above is live. If we ever add a new one ahead of its endpoint being ready, it'll show as "coming soon" here and you can still toggle it on early - calling it before it's live just returns 404 with a message pointing back to this page.

Pricing

Prices differ for every product and service, such as each account type, number, Boost package and utility plan, and they can change when provider costs change. Developer prices, which apply to requests made with your API key, are listed next to the Agent prices and the Porta user prices with the product ID for each item.

View the full list on the pricing page and search by product name or product ID before you integrate.

Endpoint Reference

Every endpoint below is read access to data your account already owns and requires its matching scope to be enabled. All are GET except POST /telly/ask, which sends a message. Every response is JSON with a top-level success boolean.

GET /liteshop/orders requires liteshop

Your own LiteShop stores' most recent 100 orders, across every store you own, newest first.

// Response { "success": true, "orders": [ { "id": "ord_abc123", "status": "paid", "totalNgn": 15500, "createdAt": "2026-09-10T12:03:00Z" } ] }

GET /wallet/balance requires wallet

Your current Porta wallet balance, in Naira.

// Response { "success": true, "balanceNgn": 45000 }

GET /wallet/transactions requires wallet

Your most recent 100 wallet transactions, newest first.

// Response { "success": true, "transactions": [ { "id": "txn_xyz", "type": "account", "service": "Upgrade to Developer", "amountNgn": -20000, "status": "success", "createdAt": "2026-09-12T09:15:00Z" } ] }

GET /litefund/campaigns requires litefund

Your Lite Fund campaigns, with live raised/target amounts.

// Response { "success": true, "campaigns": [ { "id": "lf_1", "title": "New generator for the shop", "status": "active", "raisedAmount": 120000, "targetAmount": 400000, "createdAt": "2026-08-01T00:00:00Z" } ] }

GET /liteweb/page requires liteweb

Your LiteWeb page, its links in order, and total visit/click counts. page is null if you haven't created a LiteWeb page yet.

// Response { "success": true, "page": { "id": "lw_1", "username": "amaka", "name": "Amaka's Kitchen", "logo": "https://.../logo.png" }, "links": [ { "id": "lnk_1", "type": "instagram", "label": "Instagram", "url": "https://instagram.com/amakas", "kind": "social", "hidden": false } ], "stats": { "visits": 812, "clicks": 205 } }

GET /liteform/forms requires liteform

Your Lite Forms, most recent 100, each with its total submission count.

// Response { "success": true, "forms": [ { "id": "LFORM-1", "title": "Customer satisfaction survey", "slug": "csat-2026", "status": "published", "isPublic": true, "submissionCount": 47, "createdAt": "2026-08-15T00:00:00Z" } ] }

GET /system/invoices requires system

Your System invoices, most recent 100.

// Response { "success": true, "invoices": [ { "id": "inv_1", "status": "paid", "total": 88000, "currency": "pNGN", "dueDate": "2026-09-20", "createdAt": "2026-09-05T00:00:00Z" } ] }

GET /boost/campaigns requires boost

Your Boost campaigns, most recent 100.

// Response { "success": true, "campaigns": [ { "id": "BOOST-1", "contentType": "liteshop_product", "contentId": "prod_9", "contentLabel": "Ankara fabric bundle", "dailyBudgetNgn": 2000, "durationDays": 7, "status": "active", "expiresAt": "2026-09-19T00:00:00Z", "createdAt": "2026-09-12T00:00:00Z" } ] }

GET /accounts/orders requires accounts

Your purchased Accounts S1 & S2 inventory, most recent 100, across both servers.

// Response { "success": true, "orders": [ { "id": "ACC-1", "provider": "cruise", "productName": "Gmail PVA (Aged)", "qty": 5, "price": 12500, "status": "delivered", "purchasedAt": "2026-09-10T00:00:00Z" } ] }

GET /numbers/rentals requires numbers

Your purchased virtual numbers, most recent 100.

// Response { "success": true, "rentals": [ { "id": "NUM-1", "serviceLabel": "WhatsApp", "country": "NG", "number": "+2348012345678", "status": "active", "purchasedAt": "2026-09-12T00:00:00Z", "expiresAt": "2026-09-13T00:00:00Z" } ] }

GET /gigs requires gigs

Your Lite Gigs listings.

// Response { "success": true, "gigs": [ { "id": "gig_1", "title": "Logo design", "status": "open", "priceNgn": 25000, "createdAt": "2026-09-01T00:00:00Z" } ] }

GET /bookings/events requires bookings

Events you host on LiteBookings.

// Response { "success": true, "events": [ { "id": "evt_1", "title": "Product Launch Night", "status": "published", "eventDate": "2026-10-01T18:00:00Z", "location": "Lagos" } ] }

GET /p2p/orders requires p2p

Your P2P trade orders, on either side (buy or sell). This is trade execution status only, it does not expose Porta's AML/fraud or KYC/verification systems.

// Response { "success": true, "orders": [ { "id": "p2p_1", "status": "completed", "side": "buy", "createdAt": "2026-09-09T00:00:00Z" } ] }

POST /telly/ask requires telly

Send Ask Telly a message and get a reply, the same assistant the in-app widget uses, on the plain "Ask Telly" mode only (Ask Business/Ask Trade are never reachable through this API). Send either message (a single string) or messages (a full array of {role, content} turns for multi-turn context). Counts against your account's monthly Ask Telly credit allowance, same as the in-app widget.

// Request body { "message": "How do I set up a recurring transfer?" } // Response { "success": true, "reply": "Open Withdraw, tap Recurring Transfers, then New...", "model": "claude-haiku-4-5-20251001", "creditsRemaining": 38, "creditsLimit": 50 }

GET /requests/list requires requests

Requests you've posted (custom quote requests or catalog eSIMs), most recent 100.

// Response { "success": true, "requests": [ { "id": "a1b2c3", "name": "AT&T Prepaid (USA)", "type": "esim", "description": null, "status": "requested", "budgetUsd": 90000, "units": 1, "createdAt": "2026-09-08T00:00:00Z" } ] }

GET /payments/links requires payments

Your fiat checkout links (Lite Shop "pay for me" / "split the bill" links, across every store you own) and your crypto payment invoices, each most recent 100.

// Response { "success": true, "fiatLinks": [ { "id": "pay_1", "kind": "pay_for_me", "amountNgn": 15500, "status": "paid", "paidAt": "2026-09-10T12:03:00Z", "createdAt": "2026-09-10T11:58:00Z" } ], "cryptoInvoices": [ { "id": "inv_c1", "asset": "USDT", "chain": "TRC20", "amountCrypto": 9.5, "amountNgn": 15500, "status": "pending", "expiresAt": "2026-09-12T15:00:00Z", "createdAt": "2026-09-12T14:45:00Z" } ] }

GET /pngn/wallet requires pngn

Your pNGN wallet balance (the same figure /wallet/balance reports) plus your most recent 100 internal pNGN transfers to/from other Porta users.

// Response { "success": true, "balanceNgn": 45000, "transfers": [ { "id": "txn_t1", "service": "Transfer to @chidera", "amountNgn": -5000, "status": "success", "createdAt": "2026-09-11T00:00:00Z" } ] }

GET /convert/quote requires convert

A live rate quote between two supported currencies/assets - not a trade execution, just pricing. from/to accept NGN (or pNGN, treated the same) plus any supported crypto ticker (e.g. USDT, USDC, BTC). amount is denominated in whichever currency you're converting from. Optional network disambiguates a multi-chain asset (e.g. TRC20 vs BEP20 for USDT) when converting from NGN/pNGN into it.

// GET /api/v1/convert/quote?from=NGN&to=USDT&amount=50000 // Response { "success": true, "from": "NGN", "to": "USDT", "amount": 50000, "quote": { "ok": true, "asset": "USDT", "network": "TRC20", "ngnAmount": 50000, "cryptoAmount": 30.6, "ngnPerUnit": 1634.2, "rateType": "buy" } }

Managing Credentials

Everything below lives in the app, under Management → Developer → API Keys:

  • Generate - creates your key, secret and passkey the first time. Shown once, copy all three immediately.
  • Enable/disable a service - the toggle list next to each scope in the table above. Changes take effect immediately, no regeneration needed.
  • Regenerate - rotates all three values at once. The old key, secret and passkey stop working the instant this completes, the new ones are shown once, same as generating for the first time. Use this if you suspect a value leaked.
  • Stop - a soft, reversible pause. Requests fail immediately while stopped, but you can bring the same credential set back to life with Regenerate whenever you're ready.
  • Delete Permanently - removes your credential set entirely. This cannot be undone, you'll need to generate a fresh set (with fresh values) to use the API again.

Errors

Errors always come back as JSON with success: false and a human-readable error message.

// Example error response { "success": false, "error": "This credential set isn't enabled for \"wallet\". Turn it on under Management → Developer → API." }
StatusMeaning
401Missing or invalid credentials, check all three headers are present and correct.
403Valid credentials, but that scope isn't enabled for this request. Enable it under API Keys.
404Unknown route, or a "coming soon" scope with no live endpoint yet.
405Wrong HTTP method for that route (every endpoint is GET except POST /telly/ask).
429Rate limit exceeded, see Rate Limits.
500Something went wrong on Porta's side. Safe to retry with backoff.

Webhooks

Subscribe to events, like liteshop.order.created, so your systems find out the moment something happens, rather than polling. Configure endpoints from Management → Developer → Webhooks. Each delivery is a signed POST to your URL:

// Headers on every webhook delivery X-Porta-Signature: hex-encoded HMAC-SHA256 of the raw body, using your webhook's own secret X-Porta-Event: e.g. liteshop.order.created

Verify the signature before trusting a payload, recompute the HMAC-SHA256 of the exact raw request body using your webhook's secret (shown once, at creation, same rule as the API credentials above) and compare it to X-Porta-Signature.

// Node.js signature check const crypto = require('crypto'); function isValid(rawBody, signatureHeader, webhookSecret) { const expected = crypto.createHmac('sha256', webhookSecret).update(rawBody).digest('hex'); return expected === signatureHeader; }

A slow or unreachable receiver never blocks the underlying Porta action, deliveries are fire-and-forget with an 8-second timeout, so treat your webhook handler as best-effort notification, not a required step in your own critical path.

Rate Limits

API requests are limited to 60 requests per minute per credential set, on a fixed 60-second window. Exceeding this returns 429 with a Retry-After header (seconds until the window resets). If you need a higher limit for a production integration, contact portadigitalplc@gmail.com with your use case.

// Example 429 response // Headers: Retry-After: 42 { "success": false, "error": "Rate limit exceeded (60 requests/minute per credential set). Try again in 42s." }