encore-api

Create type-safe API endpoints with Encore.ts.

INSTALLATION
npx skills add https://github.com/encoredev/skills --skill encore-api
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Encore API Endpoints

Instructions

When creating API endpoints with Encore.ts, follow these patterns:

1. Import the API module

import { api } from "encore.dev/api";

2. Define typed request/response interfaces

Always define explicit TypeScript interfaces for request and response types:

interface CreateUserRequest {

  email: string;

  name: string;

}

interface CreateUserResponse {

  id: string;

  email: string;

  name: string;

}

3. Create the endpoint

export const createUser = api(

  { method: "POST", path: "/users", expose: true },

  async (req: CreateUserRequest): Promise<CreateUserResponse> => {

    // Implementation

  }

);

API Options

Option

Type

Description

method

string

HTTP method: GET, POST, PUT, PATCH, DELETE

path

string

URL path, supports :param and *wildcard

expose

boolean

If true, accessible from outside (default: false)

auth

boolean

If true, requires authentication

sensitive

boolean

If true, redacts request/response payloads from traces

Request/Response Patterns

Encore supports four endpoint configurations:

// Both request and response

export const createUser = api(

  { method: "POST", path: "/users", expose: true },

  async (req: CreateRequest): Promise<CreateResponse> => { ... }

);

// Response only (no request body)

export const listUsers = api(

  { method: "GET", path: "/users", expose: true },

  async (): Promise<ListResponse> => { ... }

);

// Request only (no response body)

export const deleteUser = api(

  { method: "DELETE", path: "/users/:id", expose: true },

  async (req: DeleteRequest): Promise<void> => { ... }

);

// Neither request nor response

export const ping = api(

  { method: "GET", path: "/ping", expose: true },

  async (): Promise<void> => { ... }

);

Custom HTTP Status Codes

Include an HttpStatus field in your response to return custom status codes:

import { api, HttpStatus } from "encore.dev/api";

interface CreateResponse {

  id: string;

  status: HttpStatus;

}

export const create = api(

  { method: "POST", path: "/items", expose: true },

  async (req: CreateRequest): Promise<CreateResponse> => {

    const item = await createItem(req);

    return { id: item.id, status: HttpStatus.Created };  // Returns 201

  }

);

Parameter Types

Path Parameters

// Path: "/users/:id"

interface GetUserRequest {

  id: string;  // Automatically mapped from :id

}

Query Parameters

import { Query } from "encore.dev/api";

interface ListUsersRequest {

  limit?: Query<number>;

  offset?: Query<number>;

}

Headers

import { Header } from "encore.dev/api";

interface WebhookRequest {

  signature: Header<"X-Webhook-Signature">;

  payload: string;

}

Cookies

import { Cookie } from "encore.dev/api";

interface SessionRequest {

  session?: Cookie<"session">;

  settings?: Cookie<"user-settings">;

}

Request Validation

Encore validates requests at runtime using TypeScript types. Add constraints for stricter validation:

import { api } from "encore.dev/api";

import { Min, Max, MinLen, MaxLen, IsEmail, IsURL } from "encore.dev/validate";

interface CreateUserRequest {

  email: string &#x26; IsEmail;                    // Must be valid email

  username: string &#x26; MinLen<3> &#x26; MaxLen<20>;  // 3-20 characters

  age: number &#x26; Min<13> &#x26; Max<120>;           // Between 13 and 120

  website?: string &#x26; IsURL;                   // Optional, must be URL if provided

}

Combining Validation Rules

Use &#x26; for AND logic (must pass all rules) and | for OR logic (must pass at least one):

import { IsEmail, IsURL, MinLen, MaxLen } from "encore.dev/validate";

interface ContactRequest {

  // Must be valid email OR valid URL

  contact: string &#x26; (IsEmail | IsURL);

  // Must be 5-100 chars AND be a valid URL

  website: string &#x26; MinLen<5> &#x26; MaxLen<100> &#x26; IsURL;

}

Available Validators

Validator

Applies To

Example

Min<N>

number

age: number &#x26; Min<18>

Max<N>

number

count: number &#x26; Max<100>

MinLen<N>

string, array

name: string &#x26; MinLen<1>

MaxLen<N>

string, array

tags: string[] &#x26; MaxLen<10>

IsEmail

string

email: string &#x26; IsEmail

IsURL

string

link: string &#x26; IsURL

StartsWith<S>

string

id: string &#x26; StartsWith<"usr_">

EndsWith<S>

string

file: string &#x26; EndsWith<".json">

MatchesRegexp<R>

string

code: string &#x26; MatchesRegexp<"^[A-Z]{3}$">

Validation Error Response

Invalid requests return 400 with details:

{

  "code": "invalid_argument",

  "message": "validation failed",

  "details": { "field": "email", "error": "must be a valid email" }

}

Raw Endpoints

Use api.raw for webhooks or when you need direct request/response access:

export const stripeWebhook = api.raw(

  { expose: true, path: "/webhooks/stripe", method: "POST" },

  async (req, res) => {

    const sig = req.headers["stripe-signature"];

    // Handle raw request...

    res.writeHead(200);

    res.end();

  }

);

Error Handling

Use APIError for proper HTTP error responses:

import { APIError, ErrCode } from "encore.dev/api";

// Throw with error code

throw new APIError(ErrCode.NotFound, "user not found");

// Or use shorthand

throw APIError.notFound("user not found");

throw APIError.invalidArgument("email is required");

throw APIError.unauthenticated("invalid token");

Common Error Codes

Code

HTTP Status

Usage

NotFound

404

Resource doesn't exist

InvalidArgument

400

Bad input

Unauthenticated

401

Missing/invalid auth

PermissionDenied

403

Not allowed

AlreadyExists

409

Duplicate resource

Static Assets

Serve static files (HTML, CSS, JS, images) with api.static:

import { api } from "encore.dev/api";

// Serve files from ./assets under /static/*

export const assets = api.static(

  { expose: true, path: "/static/*path", dir: "./assets" }

);

// Serve at root (use !path for fallback routing)

export const frontend = api.static(

  { expose: true, path: "/!path", dir: "./dist" }

);

// Custom 404 page

export const app = api.static(

  { expose: true, path: "/!path", dir: "./public", notFound: "./404.html" }

);

Path Syntax

  • *path - Standard wildcard: matches all paths under the prefix (e.g., /static/*path)
  • !path - Fallback routing: serves static files at domain root without conflicting with other API endpoints. Use this for SPAs where unmatched routes should serve index.html

Guidelines

  • Always use import not require
  • Define explicit interfaces for type safety
  • Use expose: true only for public endpoints
  • Use api.raw for webhooks, api for everything else
  • Throw APIError instead of returning error objects
  • Path parameters are automatically extracted from the path pattern
  • Use validation constraints (Min, MaxLen, etc.) for user input
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card