NerdPortal HTTP API
The endpoints behind the guest portal and the dashboard, with the exact request and response shapes they use.
Overview
All endpoints are served from your admin host and return JSON unless noted otherwise:
https://nerdportal.unifinerds.comThey fall into three groups. Portal endpoints are public — they are what a guest device talks to. Dashboard endpoints back the admin UI and require a signed-in session. Webhooks are inbound only, called by Authorize.Net.
On public API keys
Pro and Enterprise list API access as a plan feature. Key-based authentication is not available yet — the endpoints below authenticate with a session cookie, which makes them practical for first-party use but not for server-to-server integrations. If you are building against NerdPortal, tell us what you need; it shapes what we ship.
Authentication
Dashboard endpoints use the session cookie issued when you sign in. There are two roles, and they are checked on every request:
- client — scoped to a single account. The account is taken from the session, never from the request, so one client cannot read another's data by changing an ID
- superadmin — platform administration
A request with no session returns 401. A request with a valid session but the wrong role returns 403.
Portal endpoints are unauthenticated by design — a guest has no network access yet, so they cannot present a credential. They are scoped by the subdomain in the request.
Portal endpoints
Public. These are what a captive portal page calls.
/api/portal/{subdomain}/settingsPublicReturns the branding and form configuration for a portal. Responds with defaults if the account has not customized anything yet.
{
"businessName": "Demo Coffee Shop",
"logoUrl": null,
"backgroundColor": "#0a0f1c",
"primaryColor": "#00d4ff",
"textColor": "#ffffff",
"welcomeTitle": "Welcome to Our WiFi",
"welcomeMessage": "Connect to our free WiFi...",
"termsUrl": null,
"privacyUrl": null,
"collectPhone": false,
"collectBirthdate": false,
"requireTerms": true,
"customCss": null
}Returns 404 if the subdomain does not exist or the account is inactive.
/api/portal/submitPublicRecords a guest submission and authorises the device on the account's UniFi controller. subdomain, firstName, lastName and email are required; the rest are optional.
{
"subdomain": "demo",
"firstName": "Ada",
"lastName": "Lovelace",
"email": "ada@example.com",
"phone": "+1 555 0100",
"birthdate": "1815-12-10",
"macAddress": "00:11:22:33:44:55",
"apMac": "aa:bb:cc:dd:ee:ff",
"redirectUrl": "https://example.com"
}Returns 201:
{
"success": true,
"message": "Submission successful",
"redirectUrl": "https://yourbusiness.com"
}Controller authorisation is best-effort: if the controller is unreachable or the stored credentials are stale, the submission is still saved and the response is still 201. Check the authorized flag on the submission to tell the two cases apart.
Returns 429 when the account has used its plan's submission allowance for the current billing period, and 404 if the subdomain does not exist or is inactive.
Dashboard endpoints
All require a signed-in client session and operate on that session's account.
Submissions
/api/admin/submissionsclient sessionPaginated submissions. Query parameters: page (default 1), limit (default 15, max 100), status (authorized or pending), dateRange (default 7days), search, sortBy (default createdAt) and sortOrder (default desc).
{
"submissions": [ ... ],
"pagination": { "page": 1, "limit": 15, "total": 0, "totalPages": 0 },
"summary": { "total": 0, "authorized": 0, "pending": 0, "withPhone": 0 }
}/api/admin/submissions/exportclient sessionReturns a file download rather than a JSON envelope. Query parameters: format (csv or json, default csv), dateRange (default all) and status. Any other format returns 400.
/api/admin/statsclient sessionDashboard aggregates: totals, a 7-day series, hourly distribution and recent rows.
{
"stats": { "totalSubmissions": 0, "todaySubmissions": 0,
"averagePerDay": 0, "peakHour": "14:00" },
"chartData": [ { "day": "Mon", "submissions": 0 } ],
"hourlyData": [ { "hour": "00", "count": 0 } ],
"recentSubmissions": [ ... ]
}Portal configuration
/api/admin/brandingclient sessionCurrent portal settings: colors, welcome copy, logoUrl, termsUrl, privacyUrl, redirectUrl, sessionDuration, the collectPhone, collectBirthdate and requireTerms toggles, and customCss.
/api/admin/brandingclient sessionUpdates any subset of the above and echoes the saved settings back.
UniFi connection
/api/admin/unificlient sessionReturns controllerUrl, username, siteId, isVerified and lastVerified. The password is always returned as "********" — the stored value is encrypted and is never sent back.
/api/admin/unificlient sessionAccepts controllerUrl (must be a valid URL), username, password and siteId. Send the literal "********" as the password to leave the stored one unchanged. Any change to the connection details clears isVerified; the response sets needsVerification accordingly.
/api/admin/unifi/verifyclient sessionSigns in to the stored controller and requests site health. On success, marks the connection verified and stamps lastVerified. Returns 400 if no settings are stored or they are incomplete.
Domains
/api/admin/domainclient sessionReturns subdomain, customDomain, customDomainVerified, the defaultUrl and the activeUrl currently serving the portal.
/api/admin/domainclient sessionBody is { customDomain, useCustomDomain }. Setting useCustomDomain to false clears the domain. Changing the domain resets its verified status. A domain already claimed by another account returns 400.
/api/admin/domain/verifyclient sessionBody is { domain }. Resolves the domain's CNAME and checks it points at our portal host. On failure the response includes the record you need to create:
{
"verified": false,
"message": "No CNAME record found for this domain",
"expectedRecord": {
"type": "CNAME",
"name": "wifi.yourbusiness.com",
"value": "portal.unifinerds.xyz"
}
}Account
/api/admin/billingclient sessionCurrent subscription, usage against the plan allowance, the last 10 payments and the list of available plans.
/api/admin/settingsclient sessionReturns businessName, email, subdomain and createdAt. The matching PUT updates the profile or changes the password; a wrong current password returns 400.
/api/admin/setup/completeclient sessionOnboarding status. POST to the same path marks the setup wizard complete.
Webhooks
/api/webhooks/authnetSignatureReceives Authorize.Net notifications. Every request must carry an X-ANET-Signature header, which is verified as an HMAC-SHA512 of the raw body against the configured signature key. A mismatch returns 401 and the payload is discarded.
Handled events:
payment.authcapture.createdandpayment.priorAuthCapture.created— mark the payment completedpayment.refund.created— mark it refundedpayment.void.created,payment.declined,payment.fraud.declined— mark it failedcustomer.subscription.cancelledand.terminated— cancel the subscriptioncustomer.subscription.suspended— move it to past due
Unrecognised events are acknowledged and ignored.
Errors
Errors return a JSON body with a message field describing what went wrong. Validation failures report the first field that failed.
400— validation failed, or the request was structurally wrong401— no valid session403— signed in, but the wrong role for this endpoint404— the portal or record does not exist, or the account is inactive429— the plan's submission allowance for the period is used up500— something failed on our side
