API overview
GOLD PRO exposes one HTTP API. The web app, the three native apps, and anything you build against the platform all call the same routes — there is no separate integration API with its own contract. What is documented here is what the product itself runs on.
You never pass a tenant id. Every route derives the tenant from the credentials you present and scopes its queries to it, so a request can only ever read or write inside one shop. See Authentication for how a caller is resolved into a principal.
Base URL
All examples in this reference use:
https://goldpro.shop
www.goldpro.shop is equivalent, and the deployment host
(gold-valuation.vercel.app) and localhost:3000 in development are treated the
same way. These are the main domains.
The customer portal is served from a per-tenant subdomain instead. A request to
<slug>.goldpro.shop/dashboard is rewritten internally to
/tenant-portal/<slug>/dashboard. The slug is taken from the Host header,
which a caller controls, so it is validated before it is trusted to select a
tenant context; a malformed, reserved, or unknown slug gets a plain-text 404
rather than a rewrite.
On a tenant subdomain, every
/apipath is passed straight through the edge with no session check — each handler's own authentication is the only gate. This is deliberate: portal customers hold acustomer_tokencookie, which is not a NextAuth session and would fail the edge rule that guards the main domain. Build integrations against the main domain, where both layers apply.
Requests and responses
Send JSON with Content-Type: application/json. Responses are JSON unless noted
below, and every failure uses the envelope described in
Errors.
| Concern | Behaviour |
|---|---|
| Request bodies | JSON, except POST /api/upload and POST /api/ocr/parse, which take multipart/form-data |
| File upload | POST /api/upload with a single file part. Returns { "pathname": "..." } — a private blob path, not a public URL |
| File read | GET /api/blob?pathname=... streams the stored file under its original content type |
| PDFs | GET /api/billing/documents/{id}/pdf responds application/pdf |
| Timestamps | ISO 8601 strings |
| Versioning | None. There is no version prefix and no version header |
Blob pathnames are namespaced by tenant (<tenantId>/...) and /api/blob
refuses any pathname whose first segment is not your own tenant. Customers cannot
read blobs at all, because the namespace is per-tenant rather than per-customer.
The two mutation surfaces
GOLD PRO writes through two distinct mechanisms. Knowing which one you are looking at matters, because only one of them is reachable from outside a browser.
REST routes
Everything under /api/*. These are what the native apps, the offline sync
queue, and your integrations call. They accept either a session cookie or a
Bearer token, resolved through one shared helper.
Server actions
The web UI performs most of its writes through Next.js server actions rather than
fetch. There are 44, across seven files in src/actions/:
| File | Actions | Guard |
|---|---|---|
admin.actions.ts | 29 | requireTenantAdmin (26), requireTenantAccess (3) |
customer.actions.ts | 7 | requireTenantAccess |
tenant.actions.ts | 3 | super-admin sa_token cookie |
valuation.actions.ts | 2 | requireTenantAdmin, requireTenantAccess |
communication.actions.ts | 1 | requireTenantAccess |
emi.actions.ts | 1 | requireTenantAccess |
upload.actions.ts | 1 | requireTenantAccess |
Server actions are browser-only. requireTenantAccess and
requireTenantAdmin resolve the caller through getServerSession, which reads
the NextAuth staff session cookie, and the super-admin actions read the
sa_token cookie directly. None of them inspect the Authorization header, so a
Bearer token cannot invoke one — which is why the native apps never call them.
They are an internal surface with no stable contract.
If you are integrating, use the REST routes.
Valuation creation is REST-only
There is deliberately no valuation-creating server action.
POST /api/valuations is the sole creation path, from every client. An earlier
createValuation action persisted client-supplied figures verbatim and was
removed to close that gap: the REST handler recomputes gross, net, and total
value from the tenant's stored gold rate, clamps LTV server-side, and rejects any
purity outside 24K, 22K and 18K. A client asks for a rate basis; the server
decides what the valuation is worth.
See Valuations for the full contract.
Web and mobile share one API
/api/mobile/* looks like a parallel surface for native clients. It is not. It
covers native sign-in plus a small set of screens with no direct web equivalent:
| Route | Methods |
|---|---|
/api/mobile/auth/staff/login | POST |
/api/mobile/auth/customer/login | POST |
/api/mobile/auth/superadmin/login | POST |
/api/mobile/auth/me | GET |
/api/mobile/auth/session-status | GET |
/api/mobile/auth/logout | POST |
/api/mobile/devices | POST, DELETE |
/api/mobile/personalization | GET, PATCH |
/api/mobile/personalization/display-name | PATCH |
/api/mobile/personalization/logo | POST, DELETE |
/api/mobile/personalization/notifications | PATCH |
/api/mobile/personalization/tax-invoice | PATCH |
/api/mobile/personalization/change-requests | GET, POST |
/api/mobile/bank-branches | GET, POST, PATCH, DELETE |
/api/mobile/subscription | GET |
For everything else — valuations, customers, EMI, upload, OCR, tickets,
notifications, dashboard, billing, and the customer portal — native clients call
the same routes the web calls, sending Authorization: Bearer $TOKEN instead
of a cookie:
curl https://goldpro.shop/api/customers \
-H "Authorization: Bearer $TOKEN"
One route, one handler, one set of rules, whether the caller is a browser or an app. There is no second implementation to drift out of step.
The surface at a glance
| Namespace | Purpose | Principal |
|---|---|---|
/api/valuations | Create a valuation; read, update, report, dispatch | Staff |
/api/customers | Customer book and per-customer timelines | Staff |
/api/admin/* | Gold rates, banks, invoices, templates | Tenant admin |
/api/emi | Schedules and pending counts | Staff |
/api/upload, /api/blob, /api/ocr/parse | Media capture, private file reads, receipt OCR | Staff |
/api/notifications, /api/dashboard/summary | In-app notices, dashboard figures | Staff |
/api/tickets/* | Support tickets | Staff, customer, super admin |
/api/billing/* | Plans, quotes, checkout, purchases, documents | Tenant admin |
/api/portal/* | A customer's own valuations, EMIs, timeline, notifications | Customer |
/api/tenant-portal/* | Customer email-OTP login and logout | Public |
/api/system-gate/* | Super-admin login and logout | Public |
/api/super-admin/* | Platform operations across all tenants | Super admin |
/api/mobile/* | Native sign-in and native-only screens | All |
/api/customer-intake, /api/otp/* | Self-service intake behind a signed link | Public |
/api/contact | Access-request form | Public |
/api/onboarding/* | Checklist and product-tour state | Staff |
/api/cron/* | Scheduled jobs | CRON_SECRET |
/api/razorpay/webhook | Payment events | HMAC signature |
"Public" means the route authenticates itself in the handler — with a signed intake token, an OTP, or nothing at all — rather than requiring a session at the edge. It does not mean unauthenticated.
Automated callers
Two callers are not people, and neither uses the principal model.
/api/cron/* is authenticated by a CRON_SECRET Bearer and fails closed: if
the secret is unset the route returns 401 rather than running. Schedules live
in vercel.json:
| Path | Schedule (UTC) |
|---|---|
/api/cron/emi-reminders | 30 4 * * * |
/api/cron/sync-rates | 0 */8 * * * |
/api/cron/billing-reconcile | 15 * * * * |
/api/razorpay/webhook is authenticated by an HMAC over the raw request body,
which is why the body is read as text and only parsed once the signature passes.
It is the source of truth for whether a payment happened — the browser callback
at /api/billing/verify is a convenience so the tenant sees their plan
immediately. See Webhooks.
How to read this reference
Each endpoint page lists routes with their methods, a parameter table, a runnable
request, and a real response body. Parameter tables use the columns Name,
Type, Required, Description. Secrets appear as $TOKEN.
Start with Authentication — every other page assumes you can produce a credential — then Errors, which covers the envelope, rate limits, and the idempotency key that makes retries safe.
One thing to know before you read allowed values anywhere in this reference:
there are no database enums. Every status field in the data model is a plain
String, with its permitted values recorded in a comment beside it in
prisma/schema.prisma. Where this reference lists allowed values, they are
transcribed by hand from those comments. Treat the handler as the real authority
on what it accepts, and do not assume the database will reject an unexpected
string.