API Documentation
Tutor is a Next.js 16 (App Router) application. There is no separate API
server — every endpoint is a route handler living under src/app/api/. They run on the same
host as the storefront and admin panel, so the base URL is simply your site origin. It is a lesson
booking API: search lessons, read tutor availability, price a quote, and create a booking. All money
is in minor units (cents).
Base URL (local):
http://localhost:3000Base URL (production):https://your-domain.com
There are several families of endpoints, each authenticated differently:
| Family | Path prefix | Auth |
|---|---|---|
| Public / storefront | /api/v1/*, /api/license/status |
Open (no auth) |
| Customer / host auth | /api/v1/portal/auth/* |
Session cookie member_session (JWT) |
| Customer portal | /api/v1/customer/* |
Customer session |
| Host portal | /api/v1/host/* |
Host session (a customer who is an active host) |
| Influencer portal | /api/v1/influencer/* |
Influencer session (influencer_session) |
| Admin | /api/v1/admin/* |
Session cookie admin_token (JWT) + RBAC permission |
| Install wizard | /api/install/* |
First-run only, gated by the installer |
The public/mobile API is the non-admin surface under /api/v1. Admin endpoints are for the
back office only and are excluded from the mobile API reference.
Authentication
Tutor uses cookie-based JWT sessions for every first-party surface. Each
cookie is also accepted as an Authorization: Bearer <jwt> header for mobile clients (bearer is
checked before the cookie). There are four sessions:
| Session | Cookie | Sign-in endpoint |
|---|---|---|
| Admin | admin_token |
POST /api/v1/admin/auth/login |
| Customer | member_session |
POST /api/v1/portal/auth/login |
| Host (tutor) | member_session |
POST /api/v1/portal/auth/login |
| Influencer | influencer_session |
POST /api/v1/influencer/auth/login |
Admin session (admin_token)
Admins sign in at POST /api/v1/admin/auth/login. On success the server sets an HTTP-only
admin_token cookie holding a signed JWT. Every /api/v1/admin/* route reads that cookie, and
most write routes additionally enforce a permission (RBAC) via requirePermission(...) — there
is no global middleware, the check is per-route.
POST /api/v1/admin/auth/login
Content-Type: application/json
{ "email": "admin@example.com", "password": "••••••••" }
Customer / host session (member_session)
Customers register and verify via email OTP, then receive a member_session cookie (JWT). A
host is simply a signed-in customer who also owns an active hosts row, so hosts use the same
cookie. /api/v1/portal/auth/* handles the account lifecycle:
| Endpoint | Purpose |
|---|---|
POST /api/v1/portal/auth/register |
Create an account + send email OTP |
POST /api/v1/portal/auth/verify-otp |
Verify OTP → set session cookie |
POST /api/v1/portal/auth/login |
Sign in → set session cookie |
POST /api/v1/portal/auth/logout |
Clear the session |
POST /api/v1/portal/auth/forgot · /reset |
Password reset via OTP |
POST /api/v1/portal/auth/check-email |
Check if an email is registered |
GET /api/v1/portal/auth/social/... |
Start a social sign-in flow |
GET /api/v1/portal/me |
Current customer profile |
Interactive API reference (OpenAPI)
Tutor ships an auto-generated OpenAPI 3 document rendered with Scalar.
The spec is generated from the route handlers themselves (scripts/gen-openapi.ts, run via
npm run gen:openapi), so a mobile-app developer always has an accurate, current API surface.
| Resource | URL | Notes |
|---|---|---|
| Scalar reference | /api/docs/v1 |
Two-panel Scalar UI over the spec (admin-gated) |
| Raw OpenAPI JSON | /api/openapi.json |
Serves the generated spec.generated.json (admin-gated) |
Both are admin-gated: without a valid admin_token session the reference page redirects to
/admin/login and the spec returns 401, so the spec can't be scraped publicly. Both send
noindex, nofollow.
The generator scans every src/app/api/**/route.ts, derives each path ([id] → {id}, route
groups dropped), the exported HTTP methods, and the auth requirement (from which helper the file
calls — getSession→Admin, getCustomerId→Customer, getHostId→Host, getInfluencerId→Influencer).
It groups endpoints into mobile-app surfaces — Auth · Home & content · Listings & search ·
Bookings & checkout · Reviews · Forms · Customer app · Host app · Influencer app — and excludes
internal areas (admin, install, license, plugins, ext, cron, and the chat/analytics
routes) so the reference stays focused on the mobile/public surface.
# Regenerate after adding/renaming routes
npm run gen:openapi
Endpoint reference
The tables below list real route handlers under src/app/api/v1/. List endpoints accept query
params; :slug / :id are path params. Paginated lists return { data, meta }; detail endpoints
return { data } (or the resource directly); money is always in cents.
Listings & search (lessons)
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/listings |
Browse/search published lessons (paginated) |
GET |
/api/v1/listings/:slug |
Lesson detail |
GET |
/api/v1/listings/availability?listing_id=&from=&to= |
Bookable lesson times in a window + seats_left |
POST |
/api/v1/listings/quote |
Price a session (see below) |
GET |
/api/v1/listings/:slug/reviews · POST |
List / submit lesson reviews |
POST |
/api/v1/listings/report |
Report an issue with a lesson |
GET |
/api/v1/amenities · /api/v1/extras |
"What's included" + paid add-ons |
GET /api/v1/listings supports page, per_page (≤ 50, default 12), sort
(recommended · price_asc · price_desc · rating · reviews), q, city, category,
amenities, guests (seats needed), min_rating, instant, price_min, price_max, lang.
It returns:
{ "data": [ /* lesson cards */ ],
"meta": { "total": 42, "page": 1, "per_page": 12, "total_pages": 4, "sort": "recommended" } }
Quote prices a single lesson time server-side (the client never sends an amount):
POST /api/v1/listings/quote
Content-Type: application/json
{ "session_id": 128, "seats": 2, "booking_mode": "per_person" }
booking_mode is per_person (charge per seat) or private (a private buyout of the lesson time).
The response includes availability, seat count, subtotal, extras, and totals — all in cents.
Bookings & checkout
| Method | Path | Purpose |
|---|---|---|
POST |
/api/v1/bookings |
Create a pending booking (re-quotes server-side) |
POST |
/api/v1/bookings/checkout |
Create the booking and start payment |
POST |
/api/v1/bookings/pay |
Start / continue payment for a booking |
POST |
/api/v1/bookings/verify |
Verify a gateway payment |
GET |
/api/v1/bookings/payment-methods |
Enabled payment methods |
* |
/api/v1/bookings/paypal |
PayPal REST create/capture callbacks |
POST |
/api/v1/coupons/validate |
Validate a coupon |
A booking is keyed on a session_id + seats + booking_mode plus guest details. Seats are
claimed atomically on creation (seats_total - seats_booked >= seats), so overselling is
impossible; a race returns 409.
POST /api/v1/bookings/checkout
Content-Type: application/json
{
"session_id": 128,
"seats": 2,
"booking_mode": "per_person",
"guest_name": "Sofia Rossi",
"guest_email": "sofia@example.com",
"guest_phone": "+39 …",
"guest_country": "IT",
"extras": [ { "id": 4, "qty": 2 } ],
"coupon_code": "WELCOME10",
"special_requests": "Focus on algebra basics",
"pay_option": "deposit",
"provider": "paypal"
}
{ "reference": "Tutor-8FQ2", "public_id": "bkg_…",
"total": 12000, "deposit": 4000, "currency": "EUR", "amount": 4000, "outcome": { /* gateway */ } }
pay_option is deposit or full; the server charges the deposit only when the guest chose it
and a deposit exists (0 < deposit < total), otherwise the full amount. All figures are cents.
Reviews & forms
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/reviews |
Public reviews feed |
POST |
/api/v1/forms |
Contact / enquiry / newsletter submissions |
Content & reference data (public)
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/brand · /pages/:slug · /blogs · /faqs |
White-label branding + CMS |
GET |
/api/v1/gallery · /testimonials · /hero-sliders · /destinations |
Home & content blocks |
GET |
/api/v1/countries · /currencies · /languages · /locations |
Geo / i18n reference |
GET |
/api/v1/i18n |
UI translation catalogue |
Portal endpoints (customer / host / influencer)
Each signed-in portal has its own resource tree, scoped to the current user:
| Prefix | Representative resources |
|---|---|
/api/v1/customer/* |
dashboard, bookings, payments, wishlist, reviews, notifications, support, documents, profile |
/api/v1/host/* |
dashboard, listings, sessions, calendar, reservations, earnings, payouts, plans, subscription, insights |
/api/v1/influencer/* |
dashboard, referral-links, bookings, commissions, payouts, analytics, products |
License
| Method | Path | Purpose |
|---|---|---|
GET |
/api/license/status |
Public license status (for the frontend banner) |
GET/POST |
/api/v1/admin/license |
Admin: view / activate the domain license |
Admin (excluded from the mobile API)
Admin resources (/api/v1/admin/*) manage listings, bookings, hosts, customers, extras, coupons,
refunds (the approval queue), reviews, blogs, FAQs, gallery, hero slider, pages, menus, theme
builder, master-data, partners, testimonials, issues, leads, settings, and license. They require an
admin_token session plus the relevant RBAC permission and are not part of the mobile API
reference.
Error responses
Routes return JSON { "error": "..." } with the appropriate HTTP status (via err() / serverErr()):
| Code | Meaning |
|---|---|
400 |
Bad request / validation error |
401 |
Not signed in (missing/invalid session) |
403 |
Forbidden — missing RBAC permission |
404 |
Resource not found |
409 |
Conflict — the seats were just taken (atomic claim failed) |
500 |
Server error |
{ "error": "Those seats have just been taken." }
© CreativeCape Solutions · creative-cape.com · support@creative-cape.com