Documentation

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


That's it! You now have a complete reference for all ShipSecure API endpoints. Use these as a foundation to build your own API!