Docs/Getting Started
5-minute setup

You don't need to build auth. You never did.

Astapa is a complete backend for user auth, subscription plans, and Indonesian payments — delivered as a hosted API. Redirect users to our login page, get back a signed JWT, read the plan from it. That's the whole integration.

What you actually get

Not a list of features — a list of things you no longer have to build.

🔐

Auth you didn't write

Email/password, Google, GitHub OAuth. Signup, email verification, password resets — all hosted. You just redirect.

📋

Plans that live in the JWT

Create tiers in the dashboard. Assign them to users. The plan shows up in the token — your app just reads it.

💳

Indonesian payments, done

QRIS, GoPay, ShopeePay, bank transfers, credit cards. One API call creates a Midtrans checkout. We handle the rest.

🤖

Auth for AI agents too

MCP servers and AI agents need auth. client_credentials flow, scoped JWTs, same verification logic.

Three steps, then you're done

Seriously. Most integrations take an afternoon.

1

Create a project, grab your credentials

Go to your dashboard, create a project, add your callback URL as a redirect URI. You get a client_id (public, safe for the browser) and a client_secret (private, server only).

.envtypescript
ASTAPA_CLIENT_ID=your_client_id
ASTAPA_CLIENT_SECRET=your_client_secret   # never expose this
NEXT_PUBLIC_ASTAPA_CLIENT_ID=your_client_id
2

Redirect to our login page

When a user needs to log in, send them here. We handle the UI, the OAuth dance, email verification — everything. They come back to your app with a code.

login.tstypescript
const params = new URLSearchParams({
  client_id: process.env.NEXT_PUBLIC_ASTAPA_CLIENT_ID!,
  redirect_uri: "https://yourapp.com/callback",
  state: crypto.randomUUID(), // store in cookie for CSRF check
});

// Send the user here
window.location.href = `https://astapa.com/auth/login?${params}`;
3

Exchange the code, store the tokens

Your callback receives ?code=.... Exchange it server-side for an access token (JWT) and a refresh token. Store both in httpOnly cookies.

callback.tstypescript
const res = await fetch("https://astapa.com/api/platform/token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    grant_type: "authorization_code",
    code: searchParams.get("code"),
    client_id: process.env.ASTAPA_CLIENT_ID,
    client_secret: process.env.ASTAPA_CLIENT_SECRET,
    redirect_uri: "https://yourapp.com/callback",
  }),
});

const { access_token, refresh_token } = await res.json();
// access_token is a signed JWT — verify it with our JWKS endpoint
// store both in httpOnly cookies

What's inside the JWT

Decode any access token and you'll find everything you need. No extra API calls to identify the user or check their plan.

decoded-jwt.jsonjson
{
  "sub": "42",
  "email": "user@example.com",
  "email_verified": true,
  "project_id": "proj_abc123",
  "custom_claims": {
    "plan": "pro",
    "role": "admin",
    "feature_flags": ["beta_dashboard", "export_csv"]
  },
  "iss": "https://astapa.com",
  "aud": "your_client_id",
  "exp": 1712345678,
  "iat": 1712342078
}
Feature gating is just an if-statement
Read custom_claims.plan from the JWT. Gate features in your app. When a user upgrades, set their new plan via the Claims API, refresh the token, done. No database queries, no extra API calls.

Tokens expire. Here's what to do about it.

Access tokens last 1 hour. Refresh tokens last much longer. Your middleware should silently refresh before the access token expires — the user never notices.

Refresh silently
Call POST /api/platform/token with grant_type=refresh_token. You get a fresh JWT with the latest claims baked in.
Instant plan upgrades
Set the new plan via Claims API, then immediately refresh the token. The user sees their new plan without waiting for the old JWT to expire.
Logout properly
Call POST /api/platform/revoke to kill all refresh tokens, then clear your cookies. The user can't refresh anymore — they're out.

Use sandbox while you build

Every project has a sandbox environment with its own sandbox_client_id and sandbox_client_secret. Sandbox users are completely isolated from production. Payments route to Midtrans sandbox automatically — no real money moves.

Switching environments is just swapping credentials
Use your sandbox credentials in development, production credentials in prod. The API is identical — Astapa detects which environment you're in from the client_id.

Base URL

https://astapa.com/api/platform/...

Where to go next

Pick the thing you're building right now.

API Playground
Click "Try it" on any endpoint to get started.
Getting Started | Astapa