REST API — Complete Reference

Kraviona API
Reference

Every route consumed by the admin panel and public site. URL paths, request shapes, response envelopes, RBAC requirements, and error codes — all in one place.

Prod https://api.kraviona.com
Dev http://localhost:3123
Envelope { success, data, message }
§0

Authentication

JWT (RS256) + httpOnly refresh cookie. Access token lifetime: 15 min. Refresh token: 7 days with family-based rotation and theft detection.

POST /auth/create-account
Public

Creates a new admin account and triggers an OTP email verification.

Request Body
{ "name": "Aarav Sharma", "username": "aarav", "email": "aarav@kraviona.com", "phone": "9876543210", "password": "securePass#99" }
Response 200
{ "success": true, "data": { "userId": "u-001", "email": "aarav@kraviona.com" } }
OTP is sent to the provided email. Use POST /auth/verify-account to complete signup.
POST /auth/verify-account
Public

Verifies the OTP sent during signup. Returns access token and user profile on success.

Request Body
{ "email": "aarav@kraviona.com", "otp": "847291" }
Response 200
{ "success": true, "data": { "accessToken": "eyJhbGci...", "user": { "id": "u-001", "role": "editor" } } }
POST /auth/resend-otp
Public

Resends a one-time password. Rate-limited to 1 request per 30 s, 5 per hour per email.

Request Body
{ "email": "aarav@kraviona.com", "purpose": "signup" // or "login" }
Response 200
{ "success": true, "data": { "ok": true } }
POST /auth/login-password
Public

Authenticates via email + password (Argon2id). Sets httpOnly kra_access (15 min) and kra_refresh (7 d) cookies. Rate-limited 5/min/IP, 10/min/email.

Request Body
{ "email": "admin@kraviona.com", "password": "securePass#99" }
Response 200
{ "success": true, "data": {
  "accessToken": "eyJhbGciOiJSUzI1NiIsInR5...",
  "user": { "id": "u-001", "name": "Aarav", "email": "...", "role": "admin", "permissions": ["*"] }
} }
Sets cookies: kra_access (HttpOnly, SameSite=Strict, Path=/, Max-Age=900) · kra_refresh (HttpOnly, SameSite=Strict, Path=/auth, Max-Age=604800) · XSRF-TOKEN (non-HttpOnly, Max-Age=900)
POST /auth/login-otp
Public

Two-step OTP login. Call with just email to trigger OTP, then again with email + otp to verify. 5 wrong attempts → 15 min lockout.

Step 1 — Request OTP
{ "email": "admin@kraviona.com" }
Step 2 — Verify OTP
{ "email": "admin@kraviona.com", "otp": "123456" }
Response (Step 2) 200
{ "success": true, "data": { "accessToken": "eyJhbGci...", "user": { ... } } }
POST /auth/refresh-token
Cookie

Rotates the refresh token. Requires a valid kra_refresh cookie (no request body). Revokes the old token and issues a fresh pair. Presenting a previously-revoked token kills the entire token family and forces re-login.

Response 200
{ "success": true, "data": { "accessToken": "eyJhbGci..." } }
POST /auth/logout
Cookie

Revokes the entire refresh token family and clears cookies. No request body needed.

Response 200
{ "success": true, "data": { "ok": true } }
GET /auth/me
Bearer

Returns the current authenticated user's profile and resolved permissions list.

Response 200
{ "success": true, "data": {
  "id": "u-001", "name": "Aarav Sharma",
  "email": "aarav@kraviona.com", "role": "admin",
  "permissions": ["post.create", "post.publish", "..."]
} }
PUT /auth/edit-account
Bearer

Allows an authenticated user to update their own profile (name, phone, avatar, locale, timezone). Self-edit only — cannot change role or permissions.

Request Body (partial)
{ "name": "Aarav S.", "phone": "9876543210", "locale": "en-IN", "timezone": "Asia/Kolkata" }
Admin: Users
GET /admin/users
Beareruser.read

Paginated list of all admin users. Filter by role, status, or free-text search.

Query Parameters
role=admin status=active q=search page=1 pageSize=20 sort=-createdAt
PATCH /admin/user/role
Beareruser.update

Assigns a new role to a user. Cannot elevate a user above the requester's own permission set.

Request Body
{ "userId": "u-042", "roleSlug": "editor" }
PATCH /admin/user/:id/block
Beareruser.block

Blocks or unblocks a user. Blocked users receive 403 account_blocked on all requests until unblocked.

Request Body
{ "isBlocked": true }
DELETE /admin/user/:id
Beareruser.delete

Soft-deletes a user (sets status=inactive). The user record and audit history are preserved.

§1

Site Config

Singleton documents keyed by key. Covers Hero, Nav, Footer, About, Analytics, SEO defaults, and all page-level configs. Public reads are cached 5 min.

GET /v1/public/site-config
Public

Returns the merged site-config blob containing all singleton sections. Cached at the CDN for 5 minutes. Used by the public site to bootstrap.

Cache is busted automatically when PUT /v1/site-config is called.
PUT /v1/site-config
BearersiteConfig.update

Full replace or deep-merge of the site config. Accepts Partial<SiteConfig.data>. Busts the public cache on success.

Request Body (example)
{ "hero": { "headline": "Build. Ship. Grow.", "subtext": "..." } }
GET /v1/public/:key
Public

Fetch a single singleton section by key. Available keys:

home about contact blog-page services-page pricing-page case-studies-page gallery-page footer nav socials newsletter analytics address contactInfo maintenance
PUT /v1/site-config/:key
BearersiteConfig.update

Updates a single config section by key. Body: { "data": { ... } }. Writes an audit log entry with changed field paths.

Request Body
{ "data": { "ctaLabel": "Get Started", "ctaUrl": "/contact" } }
§2

Services

Core service offerings. Each service can have per-service landing page config embedded in pageConfig.

GET /v1/public/services
Public

Fetch all active services for the public-facing site, sorted by display order.

Query Parameters
featured=true active=true page=1 pageSize=20 sort=order
GET /v1/public/services/nav
Public

Returns the navbar subset only (_id, name, slug, order). Cached for faster nav renders.

GET /v1/public/services/:slug
Public

Returns a single service with its merged pageConfig (used by per-service landing pages).

GET /v1/services
Bearerservice.read

Admin list of all services including inactive/draft ones. Supports standard pagination and filtering.

POST /v1/services
Bearerservice.create

Creates a new service. Slug is auto-generated from name if not provided.

Request Body (key fields)
{ "name": "MERN Stack Development", "slug": "mern-stack", "shortDesc": "...", "isActive": true, "featured": false }
PUT /v1/services/:id
Bearerservice.update

Full update of a service. Also accepts the full pageConfig blob for per-service landing page management.

DELETE /v1/services/:id
Bearerservice.delete

Soft-deletes (sets isActive=false). The service remains in the DB and can be restored.

PUT /v1/services/reorder
Bearerservice.update

Reorders services by providing a full ordered array of IDs. Sets the order field on each document atomically.

Request Body
{ "order": ["id_1", "id_2", "id_3"] }
§3

Portfolio, Gallery & Case Studies

Content showcasing past work. Portfolio items, curated gallery collections, and long-form case studies all follow the same CRUD pattern.

Portfolio
GET /v1/public/portfolio
Public
Query Parameters
featured=true industry=fintech q=search page=1 pageSize=20
POST /v1/portfolio
Bearerportfolio.create

Creates a new portfolio item.

PUT /v1/portfolio/:id
Bearerportfolio.update

Updates a portfolio item.

DELETE /v1/portfolio/:id
Bearerportfolio.delete

Soft-deletes a portfolio item.

Case Studies
GET /v1/public/case-studies
Public

Lists all published case studies.

POST /v1/case-studies
BearercaseStudy.create

Creates a new case study.

PUT /v1/case-studies/:id
BearercaseStudy.update

Updates an existing case study.

DELETE /v1/case-studies/:id
BearercaseStudy.delete

Deletes a case study.

Gallery
GET /v1/public/galleries
Public

Lists all published gallery collections.

POST /v1/galleries
Bearergallery.create

Creates a new gallery collection.

§4

Team & Testimonials

Team member profiles and client testimonials. Testimonials require an approve step before appearing publicly.

Team
GET /v1/public/team
Public

Returns all active team members in display order.

POST /v1/team
Bearerteam.create

Creates a new team member.

PUT /v1/team/reorder
Bearerteam.update

Reorders team members by providing a full ordered array of IDs.

Request Body
{ "order": ["id_1", "id_2", "id_3"] }
Testimonials
GET /v1/public/testimonials
Public
Query Parameters
featured=true status=approved
PUT /v1/testimonials/:id/approve
Bearertestimonial.approve

Approves a testimonial so it appears on the public site. Transition: pending → approved.

§5

Pricing

Pricing plan management. All money values are stored as integer minor units (paise). Currency is on the pricing.currency field.

GET /v1/public/pricing
Public
Query Parameters
active=true sort=order
POST /v1/pricing
Bearerpricing.create

Creates a new pricing plan. Price values in minor units (paise).

PUT /v1/pricing/:id
Bearerpricing.update

Updates a pricing plan.

DELETE /v1/pricing/:id
Bearerpricing.delete

Deletes a pricing plan.

§6

Blog — Posts, Categories, Comments

Post body is TipTap JSON (bodyJson) plus the rendered HTML (body). Legacy paths (/posts, /post/*) are kept at root for SEO compatibility.

Posts
GET /posts
Public

Paginated public post list. Only returns status=published entries unless an authenticated admin request.

Query Parameters
category=slug status=published q=search tag=javascript page=1 pageSize=20 sort=-publishedAt
GET /post/:slug
Public

Fetches a single post by slug. Increments the post's views counter on each unique visit (tracked via visitor cookie).

PUT /post/reaction/:slug
Public

Adds an emoji reaction to a post. One reaction per visitor per post (tracked via kra_vid cookie).

Request Body
{ "type": "👍" // or ❤ 🔥 💡 }
POST /post/create
Bearerpost.create

Creates a new blog post. Requires both TipTap rendered HTML (body) and the raw JSON (bodyJson). Post starts as draft unless status="published" is set and the user has post.publish permission.

Request Body (key fields)
{ "title": "MERN Stack Best Practices", "slug": "mern-stack-best-practices",
  "body": "<p>...</p>", "bodyJson": { ... },
  "category": "cat-id", "status": "draft",
  "seo": { "metaTitle": "...", "metaDescription": "..." } }
PUT /post/:slug
Bearerpost.update

Updates an existing post. Changing status to published additionally requires post.publish.

DELETE /post/:slug
Bearerpost.delete

Soft-deletes a post by slug.

PUT /keywords/:slug
Bearerpost.update

Updates the focus keywords for a post's SEO scoring. Max 8 keywords.

Request Body
{ "focusKeywords": ["mern", "node.js", "react"] }
GET /v1/admin/posts
Bearerpost.read

Admin list that includes drafts, scheduled, and archived posts. Supports all standard filtering and pagination params.

Categories
GET /categories/public
Public

Returns all active categories (used in the blog sidebar/filters).

GET /categories/admin
Bearercategory.read

Admin list including inactive categories.

POST /category/new
Bearercategory.create

Creates a new post category.

Comments
POST /v1/public/posts/:slug/comments
Public

Creates a public comment. Returns status="pending" — requires admin approval before appearing publicly.

Request Body
{ "author": "John Doe", "email": "john@example.com", "body": "Great article!" }
GET /admin/comments
Bearercomment.read
Query Parameters
post=postId status=pending page=1
PUT /admin/comment/:id
Bearercomment.update

Approves, rejects, or marks a comment as spam.

Request Body
{ "status": "approved" // or "rejected" | "spam" }
§7

Media

File upload to S3, media library management. Uploads are streamed (never buffered). ClamAV scan runs async in a worker. Max: 50 MB (images), 200 MB (video).

GET /files
Bearermedia.read
Query Parameters
folder=Marketing kind=image q=hero starred=true page=1 pageSize=40
POST /upload
Bearermedia.upload

Uploads a file to S3 via multipart/form-data. Server streams directly to S3. Returns the new media document with a scan.status="pending"; the ClamAV worker updates the row asynchronously.

Form Fields
file (required) folder alt caption tags
Response 201
{ "success": true, "data": {
  "_id": "f-001", "url": "https://cdn.kraviona.com/...",
  "variants": [{ "name": "thumb", "url": "...", "width": 320 }],
  "scan": { "status": "pending" }
} }
Magic-byte check is performed server-side regardless of Content-Type header. Virus-positive files are deleted and return 502 { code: "upload_failed", meta: { reason: "virus_detected" } }.
PUT /files/:id
Bearermedia.update

Updates metadata for a file. Does not replace the file binary.

Request Body
{ "alt": "Hero illustration", "caption": "Q1 2026", "folder": "Marketing", "starred": true }
DELETE /files/:id
Bearermedia.delete

Removes the file from S3, deletes all variants, and removes the DB record. This action is irreversible.

POST /files/:id/variants
Bearermedia.upload

Re-triggers the transcode worker for an existing file (useful if a variant failed). The worker runs async and emits progress via Socket.IO.

§8

Messages & Leads

Current implemented contact and lead capture endpoints. Public create routes do not require login and do not return saved private data. Read, update, and delete routes require an authenticated admin or super_admin.

Contact Messages
POST /api/v1/messages
Public

Creates a normal website contact message. Use for the contact page form. No auth required. The public response intentionally does not echo submitted user data.

Request Body
{ "firstName": "User", "lastName": "Name",
  "email": "user@example.com", "phone": "+919608553167",
  "subject": "Project inquiry",
  "message": "I want to discuss a MERN stack project." }
Response 201
{ "message": "Message created successfully", "success": true }
GET /api/v1/messages
Cookieadmin/super_admin

Lists stored contact messages. Requires logged-in admin or super admin.

Query Parameters
status=unread search=user@example.com page=1 limit=10
GET /api/v1/messages/:id
Cookieadmin/super_admin

Fetches one contact message by Mongo id.

PATCH /api/v1/messages/:id
Cookieadmin/super_admin

Partially updates a contact message. Common use: change status.

Request Body (partial)
{ "status": "read" }
Allowed Status
unread read replied archived
DELETE /api/v1/messages/:id
Cookieadmin/super_admin

Deletes a contact message. Requires logged-in admin or super admin.

Leads
POST /api/v1/leads
Public

Creates a sales lead. Use this for service popup forms and service page lead capture. No auth required. The public response intentionally does not echo submitted user data.

Required Fields
name email phone subject message
Request Body
{ "name": "User full name",
  "email": "user@example.com",
  "phone": "+919608553167",
  "subject": "Lead Inquiry: MERN Stack Development",
  "message": "Service: MERN Stack Development\nBudget: Need guidance\nCompany: Company Name\n\nUser requirement\n\nLead source: current page URL",
  "leadType": "service-popup",
  "page": "/services/mern-stack-development",
  "service": "MERN Stack Development",
  "budget": "Need guidance",
  "company": "Company Name" }
Response 201
{ "message": "Lead created successfully", "success": true }
GET /api/v1/leads
Cookieadmin/super_admin

Lists leads for the admin dashboard.

Query Parameters
status=New source=Website leadType=service-popup service=MERN Stack Development search=user@example.com page=1 limit=10
GET /api/v1/leads/:id
Cookieadmin/super_admin

Fetches one lead by Mongo id.

PATCH /api/v1/leads/:id
Cookieadmin/super_admin

Partially updates a lead. Common admin use: move pipeline status, add notes, score the lead, or update deal value.

Request Body (partial)
{ "status": "Contacted",
  "score": 70,
  "notes": "Called once. Asked for proposal.",
  "dealValue": 50000 }
Allowed Status
New Contacted Qualified Proposal Won Lost
DELETE /api/v1/leads/:id
Cookieadmin/super_admin

Deletes a lead. Requires logged-in admin or super admin.

§9

Subscribers & Campaigns

Email newsletter subscriptions with double opt-in, and campaign management with scheduling and send stats.

Subscribers
POST /v1/public/newsletter/subscribe
Public

Creates a new newsletter subscription with double opt-in. A confirmation email is sent automatically. Rate-limited.

Request Body
{ "email": "reader@example.com", "name": "Jane", "source": "blog-footer" }
Response 200
{ "success": true, "data": { "confirmTokenSent": true } }
GET /v1/public/newsletter/confirm
Public

Confirms a subscription via token sent in the confirmation email. Transitions pending → confirmed.

Query Parameters
token=abc123...
GET /subscribers
Bearersubscriber.read
Query Parameters
status=confirmed q=email page=1
Campaigns
GET /admin/campaigns
Bearercampaign.read
Query Parameters
status=draft q=search page=1
POST /admin/campaigns
Bearercampaign.create

Creates a new email campaign in draft status.

POST /admin/campaigns/:id/send
Bearercampaign.send

Enqueues the email.sendCampaign BullMQ job. Requires an Idempotency-Key header. Can be scheduled for the future.

Request Body (optional)
{ "scheduledFor": "2026-07-01T10:00:00Z" }
GET /admin/campaigns/:id/stats
Bearercampaign.read

Returns delivery, open, and click counters for a sent campaign.

§10

Notifications

In-app admin notifications. REST for initial load + count; live updates via Socket.IO /notifications namespace.

GET /admin/notifications
Bearer

Returns paginated notifications with the total unread count for the badge.

Query Parameters
read=false type=lead page=1
Response 200
{ "success": true, "data": { "items": [...], "totalUnread": 5, "total": 42 } }
PATCH /admin/notifications/:id/read
Bearer

Marks a single notification as read.

PATCH /admin/notifications/read-all
Bearer

Marks all of the current user's notifications as read at once.

§11

Analytics

Lightweight first-party analytics. Public tracking pixel + admin read endpoints. No third-party dependency.

POST /track/event
Public

Client-side tracking pixel. Sets a kra_vid cookie (1 year) as a visitor ID if absent. Rate-limited.

Request Body
{ "kind": "pageview", "path": "/blog/mern-stack",
  "referrer": "https://google.com",
  "utm": { "source": "twitter", "campaign": "q2" } }
GET /admin/analytics/pageviews
Beareranalytics.read
Query Parameters
from=2026-01-01 to=2026-06-30 path=/blog granularity=day
Response 200
{ "success": true, "data": { "series": [{ "ts": "2026-06-01T00:00:00Z", "count": 342 }] } }
GET /admin/analytics/summary
Beareranalytics.read

Returns the KPI summary used on the /dashboard home page.

Response 200
{ "total": 18421, "today": 312, "changePct": +12.3, "byDay": [...] }
§12

Audit Logs

Immutable, HMAC-chained audit trail. Every state-changing admin action is recorded. Read-only at the API level — no write or delete endpoints exist, even for super-admins.

GET /admin/audit
Beareraudit.read

Paginated audit log with filtering. Prefix match on action (e.g. post.* returns all post actions). Each row includes hmac and prevHash for chain verification.

Query Parameters
actor=userId action=post.* target=docId targetKind=Post from=2026-01-01 to=2026-06-30 page=1 sort=-createdAt
Response item shape
{ "action": "post.publish", "actor": { "id": "u-001", "name": "Aarav" },
  "targetKind": "Post", "target": "post-id",
  "meta": { "fromStatus": "draft", "toStatus": "published" },
  "hmac": "sha256:...", "prevHash": "sha256:...", "createdAt": "..." }
Rows are never writable. The DB user has only insert, find, count on the auditlogs collection.
GET /admin/audit/:id
Beareraudit.read

Fetch a single audit log row with full HMAC chain metadata.

§13

Settings

Grouped key/value settings. Public slice exposes analytics-enabled flag and social links. Admin read/write accesses all groups.

GET /settings/public
Public

Returns the public-safe settings slice: analytics enabled flag, social links, and other front-end config.

GET /admin/settings
Bearersettings.read

Returns all settings groups in one response.

PUT /admin/settings
Bearersettings.update

Partial merge per group. Only the keys provided are updated.

Request Body
{ "general": { "siteName": "Kraviona", "supportEmail": "hi@kraviona.com" } }
PUT /admin/settings/:key
Bearersettings.update

Updates a single setting by key.

Request Body
{ "value": "dark" }
§14

SEO

Global SEO settings, sitemap and robots.txt management, and per-page SEO audit scoring.

GET /v1/public/seo
Public

Returns merged SEO settings used by the public site to render <head> tags (defaultTitle, titleTemplate, defaultOgImage, etc.).

PUT /admin/seo
Bearerseo.update

Updates global SEO settings. Automatically triggers sitemap.rebuild job on save.

Request Body (partial)
{ "robotsPolicy": "index_follow", "noindexPages": ["/thank-you"], "defaultOgImage": "https://cdn..." }
POST /admin/seo/sitemap/rebuild
Bearerseo.update

Manually enqueues a sitemap rebuild BullMQ job. CDN cache for /sitemap.xml is invalidated on completion.

GET /admin/seo/audit
Bearerseo.read

Returns on-page SEO audit for a specific path: title length, meta description length, canonical URL, schema.org presence, and image alt coverage.

Query Parameters
path=/blog/mern-stack
GET /sitemap.xml
Public

Cached at the CDN. Invalidated when a sitemap rebuild job completes.

GET /robots.txt
Public

Generated dynamically from SeoSettings.robotsPolicy and noindexPages. Cached at edge.

§15

Content Decay

Monitors content freshness and flags stale pages for review. Scores are computed nightly by a BullMQ worker.

GET /admin/content-decay
BearercontentDecay.read

Returns a list of stale content items with their decay scores (0–100, lower = more stale).

Query Parameters
collection=posts minScore=0 maxScore=40
PUT /admin/content-decay/rules
BearercontentDecay.update

Updates decay rules (weights for age, traffic drop, link rot, etc.).

POST /admin/content-decay/recompute
BearercontentDecay.update

Enqueues a one-off BullMQ recompute job, bypassing the nightly schedule.

§16

Tech Stack

Technologies and tools to showcase in the public site. Categorized and orderable.

GET /v1/public/tech-stack
Public
Query Parameters
category=frontend active=true
POST /v1/tech-stack
BearertechStack.create

Adds a new technology entry.

PUT /v1/tech-stack/:id
BearertechStack.update

Updates a tech stack entry.

DELETE /v1/tech-stack/:id
BearertechStack.delete

Deletes a tech stack entry.

§17

Dashboard (Composite)

Convenience aggregation endpoints that bundle multiple data sources into one call, preventing the dashboard from making 10 parallel requests on load.

GET /admin/dashboard/summary
Bearer

Returns everything the /dashboard home page renders: KPIs, recent leads, recent posts, recent audit rows, and unread notification count — all in one response.

Response 200
{ "kpis": { "pageviews": 18421, "leads": 247, "posts": 68 },
  "recentLeads": [...], "recentPosts": [...],
  "recentAudit": [...], "notifications": { "unread": 3 } }
Pure read projection. Never write through these endpoints.
GET /admin/dashboard/activity
Bearer

Returns a recent activity feed across all modules (posts published, leads received, settings changed, etc.).

Query Parameters
limit=20
§18

Error Reference

All errors share the same envelope. The code field is machine-readable and stable across versions. The message is human-readable. errors[] carries per-field validation failures.

Error Envelope
{ "success": false, "data": null, "message": "Validation failed",
  "code": "validation_failed", "requestId": "01HX0Q4Z3E3RD6P5SJ6B8B3Q7V",
  "errors": [{ "path": "email", "message": "Invalid email" }] }
HTTP code When
200OK
201Created — POST that returned a new resource
204No content — empty DELETE responses
400validation_failedZod schema failed — errors[] has field paths
400bad_jsonRequest body is not valid JSON
401unauthorizedNo token, expired token, or invalid RS256 signature
401token_revokedRefresh token family was killed (theft detection)
403forbiddenAuthenticated but missing the required permission
403account_blockedUser's isBlocked=true
403csrf_failedMissing or mismatched X-CSRF-Token header
404not_foundResource doesn't exist
409conflictDuplicate slug or unique index violation
409version_mismatchOptimistic concurrency — If-Match header didn't match
410goneSoft-deleted resource (treat as gone for public)
413payload_too_largeBody > 1 MB, or upload exceeds size limit
415unsupported_media_typeWrong Content-Type for upload
422unprocessable_entitySemantic check failed (campaign in the past, post without category)
429rate_limitedPer-IP or per-user rate limit hit. meta.retryAfter in seconds
429otp_lockedToo many OTP attempts — locked for 15 min
500internal_errorUnhandled — logged with full stack, never returned to client
502upstream_errorS3 / SMTP / third-party call failed
503service_unavailableDB or Redis is down
504upstream_timeoutDownstream service timed out