Input Validation
Zod-based request validation, error sanitization, and redirect URL validation across all API routes.
Overview
Every API endpoint validates its input using Zod schemas before any business logic executes. Invalid requests are rejected immediately with a descriptive error in development and a generic error in production.
Validation Pattern
All route handlers follow the same pattern:
import { z } from "zod";
const schema = z.object({
name: z.string().min(1).max(100),
slug: z.string().regex(/^[a-z0-9-]+$/),
baseUrl: z.string().url(),
});
export const POST = async (req: NextRequest) => {
const body = await req.json();
const parsed = schema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: "Invalid input", details: parsed.error.flatten() },
{ status: 400 }
);
}
// Business logic uses parsed.data (typed and validated)
};By using safeParse instead of parse, routes return structured errors instead of throwing exceptions.
Validated Endpoints
| Endpoint | Validated Fields |
|---|---|
POST /api/dashboard/plugins | name, slug, baseUrl, description, logoUrl, category, toolSource |
PUT /api/dashboard/plugins/[pluginId] | name, slug, baseUrl, description, logoUrl, category, toolSource |
POST /api/dashboard/api-keys | name |
DELETE /api/dashboard/api-keys | keyId |
PUT /api/dashboard/plugins/[pluginId]/auth-methods | label, type, config, isDefault, authMethodId |
DELETE /api/dashboard/plugins/[pluginId]/auth-methods | authMethodId |
PUT /api/dashboard/plugins/[pluginId]/tools | name, description, method, path, parameters, toolId |
DELETE /api/dashboard/plugins/[pluginId]/tools | toolId |
POST /api/connect/submit | developerId, userId, pluginSlug, authMethodId, credentials, connectToken |
POST /api/connect/oauth-start | pluginSlug, authMethodId, connectToken, redirectUrl aliases, mcpClientSecret |
POST /api/v1/connections/initiate | pluginSlug, userId, connectionType, authMethodId, redirectUrl aliases |
DELETE /api/v1/connections | userId, plugin, connectionId |
DELETE /api/dashboard/connections | connectionId |
Error Sanitization
In production, error responses are sanitized to prevent leaking internal details:
export const safeErrorResponse = (
error: unknown,
fallbackMessage: string = "Internal server error",
status: number = 500
): NextResponse => {
if (process.env.NODE_ENV === "production") {
return NextResponse.json({ error: fallbackMessage }, { status });
}
// In development, include full error details for debugging
const message = error instanceof Error ? error.message : String(error);
return NextResponse.json({ error: message }, { status });
};This prevents stack traces, SQL queries, and internal error messages from reaching clients in production.
Redirect URL Validation
All endpoints that accept a final return URL validate it before use. The preferred field is redirectUrl; returnUrl, return_url, callbackUrl, and callback_url are accepted as aliases. If the final return URL is missing, NIN can fall back to X-NIN-Redirect-URL, X-Return-URL, Referer, then Origin.
Strict Mode (Dashboard Flows)
Used for dashboard-initiated connections where the redirect must go back to the same origin:
- Protocol must be
http:orhttps: - Origin must match
NEXT_PUBLIC_APP_URL
Relaxed Mode (SDK Flows)
Used for SDK-initiated connections where the developer controls the redirect destination:
- Protocol must be
http:orhttps: - In production, protocol must be
https:(no plaintext redirects) - In development,
http://localhostis allowed - If a request accidentally sends the NIN app origin as the final return URL and a valid requester URL is present, the requester URL is used instead
OAuth Callback Redirects
The OAuth callback endpoint (/api/v1/oauth/callback) validates the final return URL before issuing a redirect. Invalid URLs return a 400 response instead of following the potentially malicious URL.