API Cheat Sheet

By the Syncfusion DevTutorials team · Last updated

One page with the methods, URL rules, error shape, headers and side-by-side C# and Hono snippets you reach for every day.

The API Cheat Sheet is a quick reference guide that brings together the most important API rules, patterns, headers, status codes, and code examples used throughout this course. Keep it nearby while building APIs. Instead of searching through multiple lessons, you can use this page to quickly remember common practices, correct response codes, useful headers, validation patterns, and API design rules. The examples use the Brew & Bytes coffee shop API, but the same principles apply to almost every REST API you build.

HTTP Methods

MethodUse It ForSafe?Idempotent?Typical Success Code
GETRead dataYesYes200 OK or 304 Not Modified
HEADRead only response headersYesYes200 OK
OPTIONSCheck supported methods or perform a CORS preflight requestYesYes204 No Content
POSTCreate a resource or start an actionNoNo201 Created
PUTReplace an entire resourceNoYes200 OK or 204 No Content
PATCHUpdate part of a resourceNoNot always200 OK
DELETERemove a resourceNoYes204 No Content

Quick Rules

  • Safe means the request does not change data.
  • Idempotent means sending the same request multiple times produces the same final result.
  • Always use an Idempotency-Key when retrying critical POST requests such as payments.
  • Never use GET to create, update, or delete data.

See: HTTP Methods

URL Design Rules

RuleGoodBad
Use nouns, not verbs/orders/getOrders
Use plural resource names/customers/customer
Use lowercase URLs/menu-items/MenuItems
Use hyphens between words/menu-items/menu_items
Put ids in the path/orders/105/orders?id=105
Use query strings for filters/orders?status=open/open-orders
Keep nesting simple/customers/7/orders/customers/7/orders/105/items/2/notes

Pagination

Page numbers: /orders?page=2&pageSize=20

Cursor pagination: /orders?after=105&limit=20

Always enforce a maximum page size.

See: REST API Design

Problem Details (Standard Error Format)

All API errors should use the same response format.

Content type: Content-Type: application/problem+json

Example: {"type": "https://api.brewbytes.dev/problems/validation", "title": "Your order has some problems", "status": 422, "detail": "Fix the fields below and try again.", "instance": "/orders", "errors": {"quantity": ["Quantity must be between 1 and 10."], "item": ["We do not sell Mocha Frappe."]}}

Common Fields

FieldPurpose
typeIdentifies the type of problem
titleShort error summary
statusHTTP status code
detailSpecific description of the error
instancePath that caused the error
errorsValidation errors grouped by field

Important

Do not expose stack traces, SQL queries, or internal exception details. Log the technical information on the server instead.

See: Status Codes and Validation

API Versioning Headers

HeaderDirectionExamplePurpose
X-Api-VersionRequestX-Api-Version: 2Requests a specific API version
DeprecationResponseDeprecation: trueMarks a version as deprecated
SunsetResponseSunset: Sat, 31 Jan 2027 00:00:00 GMTRemoval date
LinkResponseLink: </v2/orders>; rel="successor-version"Link to next version

Breaking Changes

The following changes usually require a new API version: renaming a field, removing a field, changing a field type, adding a required request field, and removing an endpoint.

Safe Changes

The following changes are usually backward compatible: adding optional fields, adding new endpoints, and adding optional query parameters.

See: API Versioning

Authentication Headers

API Key

Header: X-Api-Key: bb_live_7f3a...

Used for partner integrations and server-to-server communication.

Bearer Token

Header: Authorization: Bearer eyJhbGciOi...

Used for user authentication and JWT access tokens.

Authentication Challenge

Header: WWW-Authenticate: Bearer error="invalid_token"

Returned with: 401 Unauthorized

Quick Rules

  • Invalid or missing credentials → 401 Unauthorized
  • User authenticated but not allowed → 403 Forbidden
  • Accessing another user's resource → often return 404 Not Found
  • Always validate JWT signature, expiration, issuer, and audience

See: Authentication and Authorization

Cache Headers

HeaderExamplePurpose
Cache-Controlpublic, max-age=60Cache for 60 seconds
Cache-Controlprivate, no-storeDo not cache
ETag"menu-12"Version identifier
If-None-Match"menu-12"Client cache validation
VaryX-Api-VersionCache based on header values

Cache Example

Server reply: ETag: "menu-12" and Cache-Control: public, max-age=60

Client request: If-None-Match: "menu-12"

Server response: 304 Not Modified

No body is returned because the client's copy is already up to date.

See: Performance and Caching

Rate Limiting Headers

HeaderExamplePurpose
RateLimit-Limit100Maximum requests allowed
RateLimit-Remaining12Requests remaining
RateLimit-Reset30Seconds until reset
Retry-After30Wait time before retrying

Retry Guidelines

Retry these: 429 Too Many Requests, 502 Bad Gateway, 503 Service Unavailable and 504 Gateway Timeout.

Do not retry these: 400 Bad Request, 401 Unauthorized, 403 Forbidden and 404 Not Found.

If Retry-After is present, wait exactly that long. Otherwise use exponential backoff. See: Rate Limits and Resilience

Most Common Response Codes

StatusMeaning
200 OKRequest succeeded
201 CreatedResource created
202 AcceptedProcessing started
204 No ContentSuccess with no body
304 Not ModifiedUse cached copy
400 Bad RequestInvalid request
401 UnauthorizedAuthentication required
403 ForbiddenPermission denied
404 Not FoundResource not found
405 Method Not AllowedHTTP method not supported
409 ConflictRequest conflicts with current state
415 Unsupported Media TypeUnsupported body format
422 Unprocessable ContentValidation failed
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorUnexpected server error
502 Bad GatewayUpstream service error
503 Service UnavailableTemporary service outage
504 Gateway TimeoutUpstream timeout

C# and Hono Side by Side

Each pair does the same job. The C# side is an ASP.NET Core minimal API in Program.cs. The TypeScript side is Hono in server.ts.

Define a route

app.MapGet("/menu", () => Results.Ok(menu));
app.MapGet("/orders/{id:int}", (int id) => Results.Ok(FindOrder(id)));
app.get("/menu", (c) => c.json(menu, 200));
app.get("/orders/:id", (c) => c.json(findOrder(Number(c.req.param("id"))), 200));

Read a query string

app.MapGet("/orders", (string? status, int limit = 20) =>
    Results.Ok(orders.Where(o => status == null || o.Status == status).Take(Math.Min(limit, 100))));
app.get("/orders", (c) => {
  const status = c.req.query("status");
  const limit = Math.min(Number(c.req.query("limit") ?? 20), 100);
  return c.json(orders.filter((o) => !status || o.status === status).slice(0, limit), 200);
});

Read a JSON body into a DTO

record CreateOrder(string Item, int Quantity);

app.MapPost("/orders", (CreateOrder input) => { /* use input.Item, input.Quantity */ });
import { z } from "zod";
const CreateOrder = z.object({ item: z.string(), quantity: z.number().int().min(1).max(10) });

app.post("/orders", async (c) => {
  const input = CreateOrder.parse(await c.req.json());
  // use input.item, input.quantity
});

Return 201 Created with a Location

var order = new Order(nextId++, input.Item, input.Quantity, "placed");
orders.Add(order);
return Results.Created($"/orders/{order.Id}", order);
const order = { id: nextId++, item: input.item, quantity: input.quantity, status: "placed" };
orders.push(order);
return c.json(order, 201, { Location: `/orders/${order.id}` });

Return a Problem Details error

return Results.Problem(statusCode: 409, title: "Too late to cancel",
    detail: "The kitchen has started this order.");
const problem = { type: "about:blank", title: "Too late to cancel", status: 409,
  detail: "The kitchen has started this order." };
return c.body(JSON.stringify(problem), 409, { "Content-Type": "application/problem+json" });

Return validation errors (422)

return Results.ValidationProblem(
    new Dictionary<string, string[]> { ["quantity"] = ["Quantity must be between 1 and 10."] },
    statusCode: 422);
const result = CreateOrder.safeParse(await c.req.json());
if (!result.success) {
  const body = { title: "Your order has some problems", status: 422,
    errors: result.error.flatten().fieldErrors };
  return c.body(JSON.stringify(body), 422, { "Content-Type": "application/problem+json" });
}

Read and set a header

app.MapGet("/orders/{id:int}", (int id, HttpRequest req, HttpResponse res) =>
{
    var version = req.Headers["X-Api-Version"].ToString();
    res.Headers["X-Correlation-Id"] = Guid.NewGuid().ToString();
    return Results.Ok(FindOrder(id));
});
app.get("/orders/:id", (c) => {
  const version = c.req.header("X-Api-Version") ?? "1";
  c.header("X-Correlation-Id", crypto.randomUUID());
  return c.json(findOrder(Number(c.req.param("id"))), 200);
});

Check auth and ownership

app.MapGet("/orders/{id:int}", (int id, ClaimsPrincipal user) =>
{
    var order = FindOrder(id);
    if (order is null || order.CustomerId != user.FindFirstValue("sub"))
        return Results.NotFound();          // 404 hides other people's orders
    return Results.Ok(order);
}).RequireAuthorization();                   // no valid token: 401
app.get("/orders/:id", async (c) => {
  const token = c.req.header("Authorization")?.replace("Bearer ", "");
  const claims = token ? await verifyJwt(token) : null;   // checks signature, exp, iss, aud
  if (!claims) return c.body(null, 401, { "WWW-Authenticate": "Bearer" });
  const order = findOrder(Number(c.req.param("id")));
  if (!order || order.customerId !== claims.sub) return c.body(null, 404);
  return c.json(order, 200);
});

Send an ETag and answer 304

app.MapGet("/menu", (HttpRequest req, HttpResponse res) =>
{
    var etag = $"\"menu-{menuVersion}\"";
    if (req.Headers.IfNoneMatch == etag) return Results.StatusCode(304);
    res.Headers.ETag = etag;
    res.Headers.CacheControl = "public, max-age=60";
    return Results.Ok(menu);
});
app.get("/menu", (c) => {
  const etag = `"menu-${menuVersion}"`;
  if (c.req.header("If-None-Match") === etag) return c.body(null, 304);
  return c.json(menu, 200, { ETag: etag, "Cache-Control": "public, max-age=60" });
});

Return 429 with Retry-After

builder.Services.AddRateLimiter(o =>
{
    o.RejectionStatusCode = 429;
    o.AddFixedWindowLimiter("perKey", w => { w.PermitLimit = 100; w.Window = TimeSpan.FromMinutes(1); });
});
app.MapGet("/menu", () => Results.Ok(menu)).RequireRateLimiting("perKey");
if (used >= 100) {
  const body = { title: "Too Many Requests", status: 429, detail: "Try again in 30 seconds." };
  return c.body(JSON.stringify(body), 429,
    { "Content-Type": "application/problem+json", "Retry-After": "30" });
}

In C#, Results.Problem(...) sets application/problem+json for you. In Hono you set it yourself, so a small problem(c, status, title, detail) helper keeps every error the same.

Which API Style Should You Choose?

NeedRecommended Style
Standard CRUD APIsREST
Flexible client-driven data queriesGraphQL
High-performance service-to-service communicationgRPC
Event notifications to external systemsWebhooks
Real-time browser updatesServer-Sent Events (SSE)

General Recommendation

Choose REST by default. Use GraphQL, gRPC, Webhooks, or SSE only when their specific strengths are needed. See: REST vs GraphQL vs gRPC