REST API Guide

REST API Guide

A single consolidated guide to REST APIs: core concepts, intermediate production patterns, advanced architecture, MySQL and JSON integration, reference labs across languages, and troubleshooting challenges. Jump to any section using the pills below.

Basics

REST as architecture, not just JSON over HTTP

REST is an architectural style. The main idea is not "return JSON", but to design systems around resources, standard HTTP semantics, stateless requests, cacheable responses, and intermediaries that can improve scalability and security.

Practical reality

Many real APIs are HTTP resource APIs that follow REST strongly without implementing full hypermedia navigation everywhere. That is common in production. What matters most is consistent resource design and correct HTTP behavior.

Resource

The thing you are working with, such as users, campaigns, orders, reports, or products.

Representation

The serialized form of resource state, usually JSON, labeled by a media type such as `application/json`.

Endpoint

The URL that represents a resource or action, such as `/api/campaigns` or `/api/orders/17`.

Method

The HTTP verb that tells the server what you want to do: GET, POST, PUT, PATCH, or DELETE.

Statelessness

Each request should contain enough context to be processed correctly without relying on hidden server-side session state.

Uniform interface

Clients use consistent HTTP semantics instead of custom remote method names for every operation.

Headers

Metadata about the call such as content type, authorization token, request id, or API version.

Body

The JSON payload sent with POST, PUT, or PATCH when you create or update data.

Response

The JSON and status code returned by the server after it processes the request.

HTTP methods

Method Example endpoint Meaning
GET /api/articles Read a list of articles
GET /api/articles/25 Read one article
POST /api/articles Create a new article
PUT /api/articles/25 Replace the entire article
PATCH /api/articles/25 Update one or two fields only
DELETE /api/articles/25 Delete the article

Common status codes

Code Meaning
200 OK The request worked and the server returned data.
201 Created A new record was created successfully.
204 No Content The request worked, but there is no body to return.
400 Bad Request The input was wrong or missing required fields.
401 Unauthorized Auth is missing or invalid.
403 Forbidden The user is authenticated but not allowed to do this action.
404 Not Found The endpoint or record does not exist.
409 Conflict There is a duplicate or version conflict.
429 Too Many Requests The client hit the rate limit.
500 Internal Server Error The server failed while processing the request.

How to read a request

  • Start with the method. It tells you whether the client wants to read, create, replace, update, or delete.
  • Read the endpoint next. `/api/orders/17` means one order. `/api/orders` usually means a list or a create action.
  • Check headers for auth, content type, request id, and versioning.
  • Inspect the body only when the call sends JSON, usually in POST, PUT, or PATCH.

How to read a response

  • Check the status code first so you know whether the call worked or failed.
  • Then inspect the JSON body for `data`, `error`, `message`, `pagination`, or `meta` fields.
  • If something fails, compare the exact request body with the API documentation before retrying.
  • Save the request id or trace id if the backend provides one. It helps support teams locate the call in logs.

Conditional requests and concurrency

  • When multiple clients can edit the same row, one update can overwrite another unless the API checks a validator.
  • ETag is a common validator. Clients can send `If-None-Match` for cache validation and conditional operations.
  • This helps prevent mid-air collisions and avoids serving stale content when the resource changed.

Pragmatic CRUD mapping

  • `GET /items` lists resources and `GET /items/{id}` reads one resource.
  • `POST /items` creates a resource, while `PATCH /items/{id}` updates only the fields you send.
  • `PUT /items/{id}` is best for full replacement and `DELETE /items/{id}` is idempotent by design.

Simple real-life example

A website wants to save a lead form submission. The frontend sends a `POST` request to `/api/leads`. The backend validates the fields, stores the row in a database, and returns JSON with the newly created record.

curl request

curl -X POST https://example.com/api/leads \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "name": "Asha",
    "email": "asha@example.com",
    "source": "landing-page"
  }'

JSON response

{
  "success": true,
  "data": {
    "id": 101,
    "name": "Asha",
    "email": "asha@example.com",
    "source": "landing-page",
    "created_at": "2026-03-25T10:45:00Z"
  }
}

Intermediate

Authentication

Use bearer tokens, API keys, or signed requests so only approved clients can access data.

Pagination

Split large lists into pages or cursor windows so responses stay fast and predictable.

Filtering and sorting

Allow clients to ask for exactly the records they need instead of returning everything.

Validation

Reject missing or malformed input before it reaches the database.

Versioning

Keep `/v1` or header-based versions for breaking changes.

Caching

Use ETag, Cache-Control, or CDN caching on safe GET endpoints.

Idempotency

Protect side-effecting retries with idempotency keys so duplicate charges, orders, or events are not created.

Problem details

Use consistent error payloads instead of bespoke error shapes per endpoint.

Authentication patterns

  • Use API keys for simple server-to-server access when the use case is narrow and low-risk.
  • JWT bearer tokens are common for signed claims, but they must be protected in storage and transport.
  • Use OAuth2 when third-party or delegated access is required, especially with scopes and refresh flows.
  • PKCE matters for public clients such as mobile apps and SPAs because it protects the authorization code flow.
  • Never place sensitive tokens in public client-side code unless the token is designed for public use.
  • Return `401` for missing or invalid auth and `403` when the user is authenticated but not allowed.

Pagination and filters

  • Support `page` and `limit` or cursor-based navigation so large tables do not overload the response.
  • Add filters such as `status=active`, `country=IN`, or `search=video` to reduce noisy payloads.
  • Return total count or next cursor so the client can keep loading cleanly.
  • Keep sorting deterministic so new inserts do not break paging unexpectedly.

Validation and versioning

  • Validate types, required fields, enums, and length before writing to MySQL or a queue.
  • Return machine-friendly errors such as `field`, `code`, and `message` so the UI can react properly.
  • Keep a stable `/v1` while you build `/v2` if you need a breaking schema change.
  • Document defaults, optional fields, and deprecation dates so clients are not surprised.

Caching and performance

  • Cache safe GET requests, not write operations.
  • Use `Cache-Control`, ETag, or `Last-Modified` when a frontend repeatedly asks for mostly unchanged data.
  • Set sensible limits so one request cannot pull an entire database table into memory.
  • Log latency, slow SQL, and payload size so performance problems are measurable.

Rate limiting, retries, and idempotency

  • Return `429 Too Many Requests` when clients exceed policy and include retry guidance when possible.
  • Design write endpoints so clients can safely retry after timeouts without creating duplicate side effects.
  • For create or payment-style operations, use an idempotency key instead of trusting transport retries alone.

Content negotiation and errors

  • The `Accept` header lets the client ask for JSON, CSV export, or specialized error media types.
  • `application/problem+json` keeps errors predictable instead of inventing a different payload for every endpoint.
  • This becomes more important as multiple clients, SDKs, and partner teams depend on the same API.

Paginated JSON response example

{
  "success": true,
  "data": [
    { "id": 1, "title": "Campaign A", "status": "active" },
    { "id": 2, "title": "Campaign B", "status": "paused" }
  ],
  "pagination": {
    "page": 1,
    "limit": 2,
    "total": 18,
    "pages": 9
  }
}

Problem details example

{
  "type": "https://example.com/problems/validation-error",
  "title": "Validation failed",
  "status": 400,
  "detail": "name is required",
  "instance": "/v1/items"
}

Hypermedia in practice

  • Full HATEOAS is uncommon in many product APIs, but links still matter for pagination, related resources, and navigation.
  • The HTTP `Link` header and formats such as HAL or JSON:API are practical ways to expose link relations.
  • Even without full hypermedia, stable link patterns improve discoverability and client correctness.

Frontend `fetch()` example

This is the point where REST meets the website UI. The browser calls the endpoint, gets JSON, and re-renders cards, tables, charts, or forms.

async function loadCampaigns(page = 1) {
  const response = await fetch(`/pages/api.php?table=blogs&limit=5&page=${page}&key=YOUR_KEY`, {
    headers: {
      "Accept": "application/json",
      "X-API-Version": "1.0"
    }
  });

  if (!response.ok) {
    throw new Error(`Request failed with ${response.status}`);
  }

  const data = await response.json();
  renderCampaigns(data);
}

Advanced

Idempotency

Use an `Idempotency-Key` when the same create request might be retried. This prevents duplicate orders, payments, or conversions.

Retries and backoff

Retry only safe failures such as timeouts or transient 5xx errors, and always back off instead of retrying instantly.

Rate limiting

Protect shared infrastructure with per-key, per-IP, or per-user request limits.

Concurrency control

Prevent lost updates with row version fields, timestamps, or optimistic locking.

Observability

Attach request ids, trace ids, latency, and error tags so teams can debug distributed systems quickly.

Webhook security

Sign webhook payloads with HMAC and verify the signature before trusting the event.

Architecture choice

Choose REST, GraphQL, gRPC, async events, or streaming based on public compatibility, flexibility, and performance needs.

Idempotency and retries

If a client times out, it may retry even though the server already created the record. Idempotency lets the server recognize that duplicate attempt and return the original result instead of creating a second row.

POST /api/orders
Idempotency-Key: order-20260325-00091
Content-Type: application/json
Authorization: Bearer TOKEN

{
  "customer_id": 44,
  "amount": 199.00,
  "currency": "USD"
}

Concurrency and consistency

  • Two users updating the same record at the same time can overwrite each other if the API blindly accepts both writes.
  • Use `updated_at`, version numbers, or ETags to detect that the record changed after the client last read it.
  • Return `409 Conflict` when the client needs to re-fetch and retry with fresh data.

Webhook signature verification

REST is request-response, but production systems often pair it with webhooks so downstream systems get updates as soon as something changes.

$payload = file_get_contents('php://input');
$incomingSignature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expectedSignature = hash_hmac('sha256', $payload, $sharedSecret);

if (!hash_equals($expectedSignature, $incomingSignature)) {
    http_response_code(401);
    exit('Invalid signature');
}

Tracing and logs

When multiple services call each other, a request id or trace id is what lets you connect the browser error to the backend logs, queue message, and database write.

{
  "request_id": "req_74b2",
  "trace_id": "trace_98ee",
  "status": "error",
  "error_code": "upstream_timeout",
  "latency_ms": 1432
}

REST plus queues

A fast REST endpoint can accept the request, validate it, store a job, and return immediately while background workers do the heavier processing.

REST plus caching

High-read endpoints often sit behind CDN or Redis caching layers so the origin database is not hit for every dashboard load.

REST plus websockets

If the UI needs push updates instead of periodic fetch calls, REST usually handles CRUD while websockets or SSE handle live notifications.

Architecture patterns used beyond simple CRUD

API Gateway

Central place for auth, TLS termination, rate limits, routing, and policy enforcement.

BFF

Backend-for-Frontend keeps mobile, web, and partner clients from forcing one generic API shape.

Saga

Distributed workflows use local transactions plus compensating steps instead of one global transaction.

Service mesh

Moves mTLS, retries, telemetry, and traffic control into the proxy layer instead of every app.

REST vs GraphQL vs gRPC

Style Typical contract Strength Best fit
REST OpenAPI plus HTTP semantics Strong HTTP caching and public interoperability Public APIs and broad platform support
GraphQL Schema and client-selected operations Harder to cache at the CDN edge Client-driven data graphs
gRPC Protobuf service definition Usually internal and performance-oriented Inter-service calls and streaming RPC

Event-driven APIs

A common production pattern is REST for commands and queries, then async events for propagation. Think `OrderCreated`, `PaymentCaptured`, or `ItemUpdated` flowing through queues or brokers.

Streaming and real-time

Use SSE for simple server push over HTTP and WebSockets when you need full-duplex communication or bidirectional live interaction.

Developer experience and monetization

Public API products often combine docs, usage plans, API keys, quotas, billing, and self-serve portals. Developer experience becomes part of the product itself.

MySQL + JSON

Browser

Sends `fetch()` requests and renders JSON into cards, tables, charts, or form messages.

REST endpoint

Receives the request, validates it, runs SQL, and returns JSON with status codes.

MySQL

Stores the records. The API reads and writes rows using SQL and converts the result set into JSON.

MySQL table example

CREATE TABLE articles (
  id INT AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(200) NOT NULL,
  body TEXT NOT NULL,
  status ENUM('draft','published') DEFAULT 'draft',
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
    ON UPDATE CURRENT_TIMESTAMP
);

JSON_EXTRACT and JSON_SET

JSON columns are useful when some fields are flexible or nested. They let you store structured metadata while still updating only the path that changed.

-- Extract a JSON value
SELECT JSON_EXTRACT(metadata, '$.specs.mp') AS mp
FROM items
WHERE id = 1;

-- Update nested JSON fields
UPDATE items
SET metadata = JSON_SET(metadata, '$.flags.featured', true, '$.color', 'red')
WHERE id = 1;

Transactions for safe multi-step updates

When one API call changes multiple columns or tables, use a transaction so the write fully succeeds or fully rolls back.

START TRANSACTION;
UPDATE items SET name = 'Camera' WHERE id = 1;
UPDATE items
SET metadata = JSON_SET(metadata, '$.updatedBy', 'api')
WHERE id = 1;
COMMIT;

GET endpoint example

This is the classic "load data into the website" endpoint. It queries MySQL, fetches rows, and returns JSON.

<?php
header('Content-Type: application/json; charset=utf-8');

$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$stmt = $pdo->query('SELECT id, title, status, updated_at FROM articles ORDER BY id DESC LIMIT 10');
$rows = $stmt->fetchAll();

echo json_encode([
    'success' => true,
    'data' => $rows,
], JSON_UNESCAPED_UNICODE);

POST endpoint example

This creates a new row from a JSON body sent by a form, admin panel, or another service.

<?php
$payload = json_decode(file_get_contents('php://input'), true);

$stmt = $pdo->prepare('INSERT INTO articles (title, body, status) VALUES (:title, :body, :status)');
$stmt->execute([
    ':title' => $payload['title'],
    ':body' => $payload['body'],
    ':status' => $payload['status'] ?? 'draft',
]);

echo json_encode([
    'success' => true,
    'insert_id' => $pdo->lastInsertId(),
]);

Render JSON into the website

async function loadArticles() {
  const response = await fetch('/api/articles');
  const payload = await response.json();

  const container = document.querySelector('#article-list');
  container.innerHTML = payload.data.map((item) => `
    <article class="article-card">
      <h3>${item.title}</h3>
      <p>Status: ${item.status}</p>
    </article>
  `).join('');
}

loadArticles();

Update a record and refresh the UI

async function saveArticle(id, updates) {
  const response = await fetch(`/api/articles/${id}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(updates)
  });

  if (!response.ok) {
    throw new Error(`Update failed with ${response.status}`);
  }

  await loadArticles();
}

Near real-time refresh

REST itself is request-response, so the simplest near real-time website update is short polling.

loadArticles();
setInterval(loadArticles, 30000);

Good production pattern

  • Use REST for create, read, update, and delete actions.
  • Use MySQL indexes so filtered reads stay fast.
  • If you filter by JSON metadata often, consider generated columns plus indexes instead of scanning raw JSON every time.
  • Return JSON only, not HTML, from API endpoints.
  • Keep DB credentials on the server, never in browser JavaScript.
  • Refresh only the changed part of the page after save.

Useful local examples in this project

  • /pages/api.php shows MySQL-backed JSON output, auth checks, and table querying.
  • /api/api-example.php shows CRUD against a JSON file, useful for understanding request flow before adding MySQL.
  • /api/api-webhook.php shows how updates can trigger webhook events after data changes.

Labs

Shared data model and endpoint contract

Core entities

  • `users`: login principals
  • `items`: business objects with JSON metadata
  • `attachments`: uploaded files linked to items

Endpoint set

  • POST /oauth/token - Demo auth endpoint that returns a bearer token for tutorials and lab flows.
  • POST /v1/items - Create an item with a JSON metadata payload.
  • GET /v1/items?limit=...&cursor=... - Cursor pagination pattern for stable list traversal.
  • GET /v1/items/{id} - Read a single item, often with attachments or related records.
  • PATCH /v1/items/{id} - Partial update using merge-style behavior.
  • PATCH /v1/items/{id}/metadata - JSON_SET style update for nested JSON fields.
  • POST /v1/items/{id}/attachments - Multipart upload flow.
  • GET /v1/stream/items - SSE stream for near real-time stats or event updates.
  • /ws - WebSocket endpoint for echo or broadcast patterns.

Node.js lab

Express plus Sequelize works well for classic HTTP resource APIs, file uploads, and JSON column updates.

import express from "express";
import multer from "multer";

const app = express();
app.use(express.json());
const upload = multer({ dest: "uploads/" });

app.post("/v1/items", requireAuth, async (req, res) => {
  const item = await Item.create({ name: req.body.name, metadata: req.body.metadata || {} });
  res.status(201).json(item);
});

app.patch("/v1/items/:id/metadata", requireAuth, async (req, res) => {
  await sequelize.query(
    "UPDATE items SET metadata = JSON_SET(metadata, ?, CAST(? AS JSON)) WHERE id = ?",
    { replacements: [req.body.path, JSON.stringify(req.body.value), Number(req.params.id)] }
  );
  res.json(await Item.findByPk(req.params.id));
});

app.get("/v1/stream/items", requireAuth, async (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  const timer = setInterval(async () => {
    res.write(`data: ${JSON.stringify({ count: await Item.count() })}\n\n`);
  }, 1000);
  req.on("close", () => clearInterval(timer));
});

FastAPI lab

FastAPI fits strongly typed Python services, SQLAlchemy sessions, and built-in WebSocket support.

@app.post("/v1/items", dependencies=[Depends(require_user)])
def create_item(payload: dict, db: Session = Depends(get_db)):
    item = Item(name=payload["name"], metadata=payload.get("metadata", {}))
    db.add(item)
    db.commit()
    db.refresh(item)
    return item

@app.patch("/v1/items/{item_id}/metadata", dependencies=[Depends(require_user)])
def json_set(item_id: int, payload: dict, db: Session = Depends(get_db)):
    db.execute(
        text("UPDATE items SET metadata = JSON_SET(metadata, :path, CAST(:value AS JSON)) WHERE id = :id"),
        {"path": payload["path"], "value": json.dumps(payload["value"]), "id": item_id},
    )
    db.commit()
    return db.get(Item, item_id)

@app.websocket("/ws")
async def ws(websocket: WebSocket):
    await websocket.accept()
    while True:
        msg = await websocket.receive_text()
        await websocket.send_text(json.dumps({"type": "echo", "msg": msg}))

Laravel lab

Laravel keeps migrations, model casting, validation, auth, and file handling in one familiar stack.

Schema::create('items', function (Blueprint $table) {
  $table->id();
  $table->string('name', 190);
  $table->json('metadata');
  $table->timestamps();
});

class Item extends Model {
  protected $fillable = ['name', 'metadata'];
  protected $casts = ['metadata' => 'array'];
}

class ItemController extends Controller {
  public function update(Request $request, int $id) {
    $item = Item::findOrFail($id);
    $patch = $request->input('metadataPatch', []);
    $item->metadata = array_merge($item->metadata ?? [], $patch);
    if ($request->has('name')) $item->name = $request->input('name');
    $item->save();
    return $item;
  }
}

Client and CLI examples

Use curl first when debugging. It removes browser complexity and lets you verify the exact request shape quickly.

# Create item
curl -s http://localhost:3000/v1/items \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Camera","metadata":{"brand":"Sony","specs":{"mp":24}}}'

# JSON_SET patch
curl -s http://localhost:3000/v1/items/1/metadata \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"path":"$.flags.featured","value":true}'

OpenAPI and tooling

  • Use OpenAPI 3.1 to describe endpoints, parameters, request bodies, and response contracts.
  • Swagger UI can render interactive docs from the OpenAPI file.
  • Postman collections are useful for manual testing, while Newman runs those collections in CI.
  • OpenAPI generators can create SDKs and server stubs from the contract.
openapi: 3.1.0
info:
  title: Items API
  version: 1.0.0
paths:
  /v1/items:
    get:
      summary: List items (cursor pagination)
    post:
      summary: Create item
  /v1/items/{id}/metadata:
    patch:
      summary: Update JSON path via JSON_SET

Challenges

Troubleshooting table

Problem What you see Reason Fix
CORS blocked Browser request fails before reaching the API Missing `Access-Control-Allow-Origin` or bad preflight config Return the correct CORS headers and handle `OPTIONS` requests
401 Unauthorized Token or API key is invalid, missing, or expired Client sent wrong auth header or stale token Refresh token, check scopes, and verify header name
404 Not Found Wrong endpoint path or missing record id Route mismatch or bad URL construction Check route naming and confirm the resource exists
409 Conflict Two updates clash or a duplicate key already exists Concurrent writes or unique constraint violation Re-fetch latest state and retry safely
429 Too Many Requests The API is protecting itself from too many calls Client loop, burst traffic, or missing backoff Respect rate-limit headers and add retry delay
500 Internal Server Error The server failed while processing Unhandled exception, SQL issue, or null data path Inspect logs, request id, stack trace, and database errors
Invalid JSON Body cannot be parsed Broken commas, quotes, or content type mismatch Validate body before sending and set `Content-Type: application/json`
Stale data on page The UI shows old values after save Cache layer, no re-fetch, or race condition Invalidate cache and reload the relevant data source
Slow response Users wait too long for data Heavy SQL, no index, or large payload Add indexes, reduce fields, paginate, and cache safe reads
Duplicate events Same webhook or POST is processed twice Client retry without idempotency Use idempotency keys and dedupe on event id
BOLA / object access issue A user can access another user record by changing the ID Object-level authorization is missing Check ownership or scope on every object lookup
CORS too open Any origin can call the API in unsafe ways Allow-all policy was left enabled Restrict origins, methods, and credential behavior deliberately

Frontend checks

  • Open browser network tools and confirm the exact request URL, method, headers, and body.
  • Compare the actual request with the documented request shape.
  • Check whether the problem happens before the call, during the call, or after the response is rendered.

Backend checks

  • Log status code, latency, request id, and sanitized error message.
  • Check auth middleware, validation layer, and route matching before blaming SQL.
  • Return consistent JSON errors so the frontend can display the right message.

Database checks

  • Confirm indexes exist on fields used in filters, joins, and ordering.
  • Look for null values, schema drift, or unexpected enum values.
  • Measure query time separately from total API latency.

Production risks to watch

  • Schema changes that break older clients because fields moved or types changed.
  • Background retries that create duplicates when idempotency is missing.
  • Large JSON payloads that slow mobile clients and increase memory usage.
  • Silent cache layers returning old data after a successful write.
  • Missing rate limits that let one client overload a shared service.

Debugging order that works

  1. Reproduce with one exact request in curl, Postman, or browser devtools.
  2. Check the status code and response body before changing code.
  3. Look up the request id or trace id in logs.
  4. Inspect SQL and queue activity only after the request path is confirmed.
  5. Add the missing guardrail so the same bug does not return.

Security baseline

  • Object-level authorization is a top API risk. Never trust the resource ID alone.
  • Use TLS everywhere, protect bearer tokens carefully, and prefer PKCE for public OAuth clients.
  • Keep secrets in server-side secret stores instead of code, images, or frontend bundles.

Performance and scaling

  • Choose pagination early. Adding it late often breaks consumers.
  • Tune connection pools, reduce payload size, and cache safe reads with validators.
  • Use gateways or edge controls to enforce throttling before traffic reaches the app.

Observability and testing

  • Collect traces, metrics, and logs together so one failing request can be followed end to end.
  • Keep test coverage at unit, integration, and contract level, not only manual Postman checks.
  • Watch out for high-cardinality labels in metrics systems because they explode time series counts.

Useful production patterns from real API styles

Stripe-style pattern

Use idempotency keys so retries return the stored first result instead of double-creating charges or money movement.

GitHub and X-style pagination

Use link-based or token-based pagination when datasets are large and offset pagination becomes unstable or slow.

OpenRTB-style no-bid

AdTech APIs sometimes use `204 No Content` to signal a valid no-bid or empty response path instead of treating it as an error.

Saga pattern

For multi-step workflows across services, use compensating steps instead of trying to force one distributed transaction everywhere.

AdTech Toolkit

Enter any two values
to calculate the third

More tools coming soon