Skip to content

portal

← All modules

6 endpoints.

GET /api/v1/portal/summary

Summary

200 — Successful Response

json
{}

GET /api/v1/portal/voice-usage

Voice Usage

Call Records (CDR) stats for the signed-in customer's PBX domain(s), plus what they currently buy in monthly airtime versus what their own call history says they should.

Everything here is read live from the PBX box — call volume/minutes/spend from GET /api/v1/call-history (whole-window aggregates only, never the rows), the wallet/unlimited plan state from GET /api/v1/balance/summary. The one local input is the customer's active VOICE-AIR-R50 subscription quantity, which is what actually gets credited to the wallet each month by the portal's own native recharge job and therefore IS their current monthly airtime.

Best-effort like the rest of the Overview: a customer with no voice domain, an unreachable PBX or an unconfigured connection gets {"available": false} and the frontend simply omits the section, rather than an error that breaks the page.

200 — Successful Response

json
{}

GET /api/v1/portal/webstore

Webstore

Origin of the public webstore this portal fronts — the "Visit Webstore" / "Place an Order" buttons' target, which the frontend turns into an auto-login link by appending ?sso_token=<the caller's own portal JWT> (exchanged for a storefront session by app.api.public_storefront.sso_exchange).

The storefront lives on the BOOKS site (portal.telecloud.co.za → tc, portal.moviemagic.cc → mm), so the domain is read from that site's storefronts row rather than hardcoded: the verified custom_domain when its provisioning reached live, else the always-working temp_domain (<slug>.lubb.co.za). url: None when the books site has no storefront row, hasn't published one, or has neither domain yet — the frontend hides the buttons entirely in that case rather than linking somewhere that 404s.

200 — Successful Response

json
{}

Pay Link

The "Make a Payment" link for the signed-in customer's left-menu button — resolves the most recent Sales Invoice belonging to a linked customer that's actually payable right now, and returns the same public invoice view already linked from invoice emails/PDFs (see app.api.public_documents.view_invoice), which carries the exact same "Pay Now" button. Payability (submitted, not a credit note, Paystack-settlable currency, and the customer's whole ACCOUNT balance — not just this invoice's own total, balance-forward — actually owing something) is delegated to accounting_pdf.invoice_pay_urls, the single source of truth the invoice's own embedded Pay Now button uses, so this can never drift out of sync with it. url is an absolute link to the books site's own domain (see _books_base_url) — the invoice's pay token only resolves against tc's own DB, not the caller's (portal) site; url: None when the login has no linked customer or nothing currently payable.

200 — Successful Response

json
{}

GET /api/v1/portal/document-pdf/{voucher_type}/{voucher_id}

Document Pdf

Return the PDF of one accounting document (invoice or payment) as base64, for the portal Statement's clickable reference link. Reads cross-DB from the books site like everything else on the portal; authorises by checking the document's customer is one the caller's login is linked to.

Only acc_sales_invoice (invoice / credit note) and acc_payment_entry (payment / refund) are supported — journal entries carry no customer-facing PDF and return 404.

Parameters

NameInRequiredTypeDescription
voucher_typepathyesstring
voucher_idpathyesstring

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/portal/contact

Contact

Portal "Contact Us" form, reachable from the account menu — always goes to the central helpdesk inbox (not the per-site branding.support_email, unlike the storefront's public contact form) so every site's enquiries land in one place, tagged with the site/org they came from. Name/email are read off the authenticated session rather than trusted from the client, since this router already runs behind social_tenant_ctx (wired in main.py).

Request body

json
{
  "properties": {
    "subject": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ],
      "title": "Subject"
    },
    "message": {
      "type": "string",
      "maxLength": 5000,
      "minLength": 1,
      "title": "Message"
    }
  },
  "type": "object",
  "required": [
    "message"
  ],
  "title": "ContactIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

Lubb ERP Documentation