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
| Method | Use It For | Safe? | Idempotent? | Typical Success Code |
|---|---|---|---|---|
GET | Read data | Yes | Yes | 200 OK or 304 Not Modified |
HEAD | Read only response headers | Yes | Yes | 200 OK |
OPTIONS | Check supported methods or perform a CORS preflight request | Yes | Yes | 204 No Content |
POST | Create a resource or start an action | No | No | 201 Created |
PUT | Replace an entire resource | No | Yes | 200 OK or 204 No Content |
PATCH | Update part of a resource | No | Not always | 200 OK |
DELETE | Remove a resource | No | Yes | 204 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
GETto create, update, or delete data.
See: HTTP Methods
URL Design Rules
| Rule | Good | Bad |
|---|---|---|
| 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
| Field | Purpose |
|---|---|
type | Identifies the type of problem |
title | Short error summary |
status | HTTP status code |
detail | Specific description of the error |
instance | Path that caused the error |
errors | Validation 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
| Header | Direction | Example | Purpose |
|---|---|---|---|
X-Api-Version | Request | X-Api-Version: 2 | Requests a specific API version |
Deprecation | Response | Deprecation: true | Marks a version as deprecated |
Sunset | Response | Sunset: Sat, 31 Jan 2027 00:00:00 GMT | Removal date |
Link | Response | Link: </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
| Header | Example | Purpose |
|---|---|---|
Cache-Control | public, max-age=60 | Cache for 60 seconds |
Cache-Control | private, no-store | Do not cache |
ETag | "menu-12" | Version identifier |
If-None-Match | "menu-12" | Client cache validation |
Vary | X-Api-Version | Cache 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
| Header | Example | Purpose |
|---|---|---|
RateLimit-Limit | 100 | Maximum requests allowed |
RateLimit-Remaining | 12 | Requests remaining |
RateLimit-Reset | 30 | Seconds until reset |
Retry-After | 30 | Wait 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
| Status | Meaning |
|---|---|
200 OK | Request succeeded |
201 Created | Resource created |
202 Accepted | Processing started |
204 No Content | Success with no body |
304 Not Modified | Use cached copy |
400 Bad Request | Invalid request |
401 Unauthorized | Authentication required |
403 Forbidden | Permission denied |
404 Not Found | Resource not found |
405 Method Not Allowed | HTTP method not supported |
409 Conflict | Request conflicts with current state |
415 Unsupported Media Type | Unsupported body format |
422 Unprocessable Content | Validation failed |
429 Too Many Requests | Rate limit exceeded |
500 Internal Server Error | Unexpected server error |
502 Bad Gateway | Upstream service error |
503 Service Unavailable | Temporary service outage |
504 Gateway Timeout | Upstream 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?
| Need | Recommended Style |
|---|---|
| Standard CRUD APIs | REST |
| Flexible client-driven data queries | GraphQL |
| High-performance service-to-service communication | gRPC |
| Event notifications to external systems | Webhooks |
| Real-time browser updates | Server-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