API Reference
Complete reference for all API endpoints included in ShipSecure.
Overview
ShipSecure includes pre-built API endpoints for authentication and billing. All API routes are located in src/app/api/ following Next.js App Router conventions.
Base URL
Development: http://localhost:3000/api
Production: https://your-domain.com/api
Authentication
All API requests require authentication via session cookies (managed by Auth.js). Unauthenticated requests return 401 Unauthorized.
Authentication API
Auth.js provides these endpoints automatically. You don't need to create them - they're handled by the [...nextauth]/route.ts handler.
Sign In
Redirects to OAuth provider for authentication.
GET /api/auth/signin
GET /api/auth/signin/github
GET /api/auth/signin/google
Usage
<!-- Link directly -->
<a href="/api/auth/signin">Sign In</a>
<!-- Or redirect programmatically -->
<script>
window.location.href = "/api/auth/signin";
</script>
Sign Out
Signs out the current user and clears session.
POST /api/auth/signout
Usage with Auth.js
import { signOut } from "next-auth/react";
// In a client component
<button onClick={() => signOut()}>Sign Out</button>
// Or with redirect
<button onClick={() => signOut({ callbackUrl: "/" })}>Sign Out</button>
Get Session
Returns the current user session.
GET /api/auth/session
Response
Authenticated:
{
"user": {
"id": "clx1234567890",
"name": "John Doe",
"email": "john@example.com",
"image": "https://avatars.githubusercontent.com/u/123456"
},
"expires": "2024-02-15T12:00:00.000Z"
}
Not Authenticated:
{}
Usage
// Server Component
import { auth } from "@/lib/auth";
const session = await auth();
// Client Component
import { useSession } from "next-auth/react";
const { data: session } = useSession();
// API Route
import { auth } from "@/lib/auth";
const session = await auth();
OAuth Callbacks
These endpoints handle OAuth provider callbacks. They're configured in your OAuth app settings.
GET /api/auth/callback/github
GET /api/auth/callback/google
Note: These URLs must match exactly in your OAuth provider settings.
CSRF Token
Returns CSRF token for form submissions.
GET /api/auth/csrf
Note: This is handled automatically by Auth.js in most cases.
Creating Custom API Routes
Basic Structure
Create a new file in src/app/api/[endpoint]/route.ts:
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
// GET request
export async function GET(req: NextRequest) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Your logic here...
return NextResponse.json({ data: "..." });
}
// POST request
export async function POST(req: NextRequest) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await req.json();
// Your logic here...
return NextResponse.json({ success: true });
}
With Input Validation
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { z } from "zod";
const createProjectSchema = z.object({
name: z.string().min(1).max(100),
description: z.string().max(500).optional(),
});
export async function POST(req: NextRequest) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await req.json();
const validatedData = createProjectSchema.parse(body);
const project = await db.project.create({
data: {
...validatedData,
userId: session.user.id,
},
});
return NextResponse.json(project, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Validation failed", details: error.errors },
{ status: 400 }
);
}
console.error("Error creating project:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 }
);
}
}
With Plan Check (PRO Only)
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
export async function GET(req: NextRequest) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Check user plan
const user = await db.user.findUnique({
where: { id: session.user.id },
select: { plan: true },
});
if (user?.plan !== "PRO") {
return NextResponse.json(
{ error: "PRO subscription required" },
{ status: 403 }
);
}
// PRO-only logic...
return NextResponse.json({ data: "PRO content" });
}
Dynamic Routes
For routes with parameters like /api/projects/[id]:
// src/app/api/projects/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await auth();
const { id } = await params;
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const project = await db.project.findFirst({
where: {
id,
userId: session.user.id, // Ensure user owns this project
},
});
if (!project) {
return NextResponse.json({ error: "Project not found" }, { status: 404 });
}
return NextResponse.json(project);
}
export async function DELETE(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await auth();
const { id } = await params;
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
await db.project.delete({
where: {
id,
userId: session.user.id,
},
});
return new NextResponse(null, { status: 204 });
}
Rate Limiting
All API routes are protected by rate limiting (see Security Features):
/api/*: 60 requests/minute/api/auth/*: 20 requests/minute
Response when rate limited (429):
{
"error": "Too Many Requests"
}
Error Handling
Standard Error Format
All API errors follow this format:
{
"error": "Error message here"
}
Validation Errors
Zod validation errors include details:
{
"error": "Validation failed",
"details": [
{
"path": ["email"],
"message": "Invalid email"
}
]
}
HTTP Status Codes
- 200 OK: Successful GET/PUT/PATCH
- 201 Created: Successful POST (new resource)
- 204 No Content: Successful DELETE
- 400 Bad Request: Invalid input/validation error
- 401 Unauthorized: Not authenticated
- 403 Forbidden: Not authorized (e.g., PRO required)
- 404 Not Found: Resource doesn't exist
- 409 Conflict: Resource conflict (e.g., duplicate)
- 429 Too Many Requests: Rate limit exceeded
- 500 Internal Server Error: Server-side error
Testing API Routes
Using Vitest
// src/app/api/projects/__tests__/route.test.ts
import { describe, it, expect, vi } from "vitest";
import { GET } from "../route";
import { NextRequest } from "next/server";
// Mock auth
vi.mock("@/lib/auth", () => ({
auth: vi.fn(() =>
Promise.resolve({
user: { id: "test-user-id" },
})
),
}));
// Mock database
vi.mock("@/lib/db", () => ({
db: {
project: {
findMany: vi.fn(() => Promise.resolve([{ id: "1", name: "Project 1" }])),
},
},
}));
describe("GET /api/projects", () => {
it("returns user projects", async () => {
const request = new NextRequest("http://localhost:3000/api/projects");
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toHaveLength(1);
expect(data[0].name).toBe("Project 1");
});
});
Using cURL
# Get session (check if authenticated)
curl http://localhost:3000/api/auth/session
Next Steps
- Database & Prisma - Learn about data models
- Security Features - Understand rate limiting
- Testing Guide - Write API tests
That's it! You now have a complete reference for all ShipSecure API endpoints. Use these as a foundation to build your own API!