Xeta API Reference

Welcome to the ultimate foundational layer. The Xeta API is organized around REST. Our API has predictable resource-oriented URLs, accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.

Production Base URL

https://api.xeta.in/v1

Sandbox Base URL

https://sandbox.api.xeta.in/v1

API Versioning

When backwards-incompatible changes are made to the API, a new, dated version is released. The current version is v1. You can set your API version globally in your Xeta Developer Dashboard.

X-Xeta-Version: 2026-04-15

Always pass the version header to ensure your application doesn't break when we push new updates.

Environments

Xeta provides two distinct environments: Production and Sandbox. The Sandbox environment operates exactly like Production but doesn't actually mutate real user data or send actual WebSocket broadcasts.

  • Production: api.xeta.in
  • Sandbox: sandbox.api.xeta.in

Authentication

The Xeta API uses API keys to authenticate requests. You can view and manage your API keys in the Xeta Developer Dashboard.

Test mode secret keys have the prefix sk_test_ and live mode secret keys have the prefix sk_live_. Your API keys carry many privileges, so be sure to keep them secure!

HTTPS Required

All API requests must be made over HTTPS. Calls made over plain HTTP will fail.

Authentication Example
# Authenticate via Bearer Token
curl https://api.xeta.in/v1/users/me \
  -H "Authorization: Bearer sk_live_98f89..."

OAuth 2.0 Scopes

Scopes provide a way to limit the amount of access that is granted to an access token. For example, an access token issued to a third-party app might be granted read access to Xora posts, but not write access.

Scope Name
Access Level
user.profile:read
Read basic user profile data (Name, Bio, Avatar).
xora.post:write
Create, modify, or delete posts on Xora.
nexus.stream:connect
Establish a WebSocket connection to the Nexus engine.

Pagination

All top-level API resources have support for bulk fetches via "list" API methods. Xeta utilizes Cursor-based pagination. Offset pagination is not supported as it scales poorly with massive datasets.

Parameters

limitInteger

A limit on the number of objects to be returned (1-100).

cursorString

A cursor for use in pagination. next_cursor defines your place in the list.

Response Object
{
  "object": "list",
  "has_more": true,
  "next_cursor": "post_9x8y7z",
  "data": [ ... ]
}

Errors

Xeta uses conventional HTTP response codes to indicate the success or failure of an API request.

  • 400 - Bad Request

    The request was unacceptable, often due to missing a required parameter.

  • 401 - Unauthorized

    No valid API key provided.

Error Schema
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_missing",
    "message": "The parameter 'user_id' is required."
  }
}

Rate Limits

The Xeta API utilizes a Leaky Bucket algorithm. If you exceed the limit, you will receive a 429 Too Many Requests response. Check the HTTP headers for your status.

HTTP Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1713160000

Webhooks

Xeta uses webhooks to notify your application when an event happens in your account.

Verifying Signatures

Xeta signs the webhook events it sends to your endpoints by including a signature in each event's Xeta-Signature header.

Node.js Signature
const crypto = require('crypto');

function verifySignature(payload, header, secret) {
  const parts = header.split(',');
  const timestamp = parts[0].split('=')[1];
  const signature = parts[1].split('=')[1];

  const signedPayload = \`${timestamp}.${payload}\`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Xora Social Graph API

Create a Post

POST/v1/xora/posts

Creates a new post on behalf of the authenticated user.

Body Parameters

contentStringRequired

The text content of the post.

Request
{
  "content": "Deploying Xeta Core! 🚀"
}

Fetch Xora Feed

GET/v1/xora/feed

Retrieves a paginated array of post objects curated by Lumina's neural recommendation engine.

WebSocket Connection

WSSwss://ws.xeta.in/v1/stream

Establish a persistent TCP connection to the Nexus engine.

Platform Internals

Everything under the hood — the real tech powering xeta.in, explained for developers and students.

Tech Stack

Xeta.in is a full-stack TypeScript application. Here's every layer of the stack, from browser to database.

FrontendNext.js 16.2 (App Router)

React 19 Server + Client Components, Tailwind CSS 4, Framer Motion

ORMPrisma 6.19

Schema-first type-safe database client. Generates TypeScript types from schema.prisma

DatabasePostgreSQL on Neon Cloud

Serverless PostgreSQL. Auto-pauses on free tier. Region: ap-southeast-1 (Singapore)

PaymentsRazorpay Subscriptions

Recurring billing API. Webhook-driven subscription lifecycle management

AuthCustom OTP + Session Cookies

Phone/email OTP via VerificationToken table. Session stored as httpOnly cookie (xeta_session)

EmailNodemailer + Gmail SMTP

Transactional emails: OTP, password reset, payment receipts, password change alerts

HostingVercel

Edge-deployed Next.js. API routes run as Node.js serverless functions

LanguageTypeScript (strict mode)

End-to-end type safety from API routes to React components

OTP Auth Flow

Xeta uses a passwordless OTP system backed by the VerificationToken table. Here's the complete flow:

  1. Send OTP

    POST /api/auth/send-otp

    Generates a 6-digit code, stores it with a 10-minute expiry in VerificationToken with identifier = phone/email, sends via SMS/email

  2. Verify OTP

    POST /api/auth/verify-otp

    Finds the token, checks expiry, deletes it (single-use), creates/updates the User record, sets xeta_session httpOnly cookie = user.id (CUID)

  3. Session Check

    cookies().get('xeta_session')

    Every API route reads this cookie to identify the logged-in user. No JWT — pure DB session lookup via Prisma

  4. Password Reset

    POST /api/auth/forgot-password

    Generates a 64-char hex token, stores with identifier = 'pwd_reset:{email}', emails a 5-minute link to /auth/reset-password?token=...&email=...

Login via OTP
// Step 1: Send OTP
await fetch('/api/auth/send-otp', {
  method: 'POST',
  body: JSON.stringify({
    identifier: 'user@email.com'
  })
});

// Step 2: Verify OTP
await fetch('/api/auth/verify-otp', {
  method: 'POST',
  body: JSON.stringify({
    identifier: 'user@email.com',
    token: '482917'
  })
});

// Cookie 'xeta_session' is now set
// All future API calls are authenticated

Subscription System

Xeta uses Razorpay's subscription API. Here's the full payment lifecycle:

POST/api/create-subscription

Creates a Razorpay subscription for the selected plan_id. Returns subscription object with id (sub_XXXX) used in the checkout modal.

POST/api/save-subscription

Called from the client after Razorpay payment success. Saves to the Subscription table with userId from session cookie, tier, amount, and Razorpay IDs.

GET/api/billing

Returns all subscriptions for the logged-in user, ordered newest first. Used by the Billing & Invoices dashboard page.

POST/api/cancel-subscription

Sets the active subscription's status to CANCELLED. No refund logic — purely a DB status change.

POST/api/webhooks/razorpay

Razorpay webhook endpoint. Verifies HMAC-SHA256 signature before processing events like subscription.charged.

Subscription Flow
// 1. Create subscription
const { id } = await
  fetch('/api/create-subscription', {
    method: 'POST',
    body: JSON.stringify({ plan_id })
  }).then(r => r.json());

// 2. Open Razorpay checkout
new Razorpay({
  key: 'rzp_test_...',
  subscription_id: id,
  handler: async (res) => {
    // 3. Save to DB
    await fetch('/api/save-subscription', { ... });
  }
}).open();

Database Schema

Xeta uses Prisma with PostgreSQL on Neon. The schema lives at prisma/schema.prisma.

model User
  • id (CUID, PK)
  • firstName, lastName, name
  • email (unique), phone (unique)
  • password (bcrypt hashed)
  • role: USER | ADMIN | ARCHITECT
  • isEmailVerified, isPhoneVerified
  • avatar (base64 JPEG string)
  • status: ACTIVE | BANNED | SUSPENDED
model Address
  • id (CUID, PK)
  • userId (FK → User)
  • address1 (required)
  • address2, address3 (optional)
  • state, country, pincode
  • isDefault (bool)
  • createdAt, updatedAt
model Subscription
  • id (CUID, PK)
  • userId (FK → User)
  • tier (plan name string)
  • amount (INR integer)
  • razorpaySubId (unique)
  • razorpayPaymentId (unique)
  • status: ACTIVE | INACTIVE | CANCELLED
  • startDate, endDate
model VerificationToken
  • id (CUID, PK)
  • identifier (email or phone)
  • token (OTP or reset token)
  • expires (DateTime)
  • Unique: [identifier, token]
  • Used for: OTP login (6-digit)
  • Also: pwd_reset:{email} prefix for password resets

For Students

Learn by studying how Xeta is built. Real production patterns — not toy examples.

Quick Start: Clone & Run Xeta Locally

terminal
# Clone the repo
git clone https://github.com/xeta-systems/xeta-corp
cd xeta-corp

# Install dependencies
npm install

# Set environment variables
cp .env.example .env.local
# Fill in DATABASE_URL, SMTP_USER, RAZORPAY keys

# Push the Prisma schema to your DB
npx prisma db push

# Start dev server
npm run dev
# → localhost:3000

Learn: Build Your Own Auth System

Xeta's auth system avoids JWT complexity. Instead it uses a simple database-backed session pattern:

  • 01 Generate a random OTP and store it in DB with an expiry time
  • 02 User submits OTP → look it up, check expiry → delete it (one-time use)
  • 03 Set an httpOnly cookie with the user's ID
  • 04 All protected routes read that cookie and look up the user

Why httpOnly cookies?

JavaScript on the page can't read httpOnly cookies — so even if your app has an XSS bug, the attacker can't steal the session token. This is safer than storing tokens in localStorage.

Next.js API Route (App Router)
import { cookies } from 'next/headers';
import { prisma } from '@/lib/prisma';

export async function POST(req) {
  const { identifier, token } = await req.json();

  // Find OTP in database
  const otp = await prisma.verificationToken
    .findFirst({ where: { identifier, token } });

  if (!otp || otp.expires < new Date())
    return Response.json({ error: 'Invalid OTP' }, { status: 401 });

  // Delete OTP (one-time use)
  await prisma.verificationToken.delete({ where: { id: otp.id } });

  // Set session cookie
  (await cookies()).set('session', user.id, {
    httpOnly: true, sameSite: 'lax'
  });
}

Learn: Integrate Razorpay in Next.js

Razorpay has two parts: a server-side API (creates subscriptions/orders) and a client-side JS SDK (opens the payment modal). Never put your key_secret on the frontend.

1. Server: Create a subscription

Use your secret key server-side only. Create a subscription with a plan_id and return its id to the client.

2. Client: Open checkout modal

Load Razorpay's JS SDK, initialize with your public key + subscription_id, and call .open(). The handler callback fires on success.

3. Server: Save the payment

After the handler fires, POST to your own API with razorpay_payment_id and razorpay_subscription_id. Verify via webhook for production.

Client-side checkout
// Load SDK in layout.tsx or via <Script>
// src="https://checkout.razorpay.com/v1/checkout.js"

async function pay(planId) {
  // Get subscription from your API
  const { id } = await fetch('/api/create-subscription',
    { method: 'POST', body: JSON.stringify({ plan_id: planId }) }
  ).then(r => r.json());

  const rzp = new window.Razorpay({
    key: 'rzp_test_YOUR_KEY',
    subscription_id: id,
    name: 'Your Company',
    handler: async (response) => {
      // response has razorpay_payment_id
      await saveToDatabase(response);
    }
  });
  rzp.open();
}