Skip to Content
Software TestingAPI Fundamentals

API Fundamentals

Modern REST API Reference and Best Practices

A comprehensive, developer-friendly guide to understanding and implementing RESTful APIs. This documentation covers authentication, endpoint design, error handling, and advanced features for building robust web services.\


Quickstart

Get started with the API in minutes. All requests use HTTPS and communicate with JSON payloads. The API follows RESTful conventions with standard HTTP methods and status codes.

Making Your First Request

curl -s \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ https://api.example.com/v1/items?limit=25

Response Format

All successful responses return JSON with a predictable structure:

{ "data": [...], "meta": { "count": 25, "next_cursor": "eyJvZmZzZXQiOjI1fQ" } }

Authentication

The API uses Bearer token authentication for secure access. Every request must include a valid API token in the Authorization header.

┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ Client │ │ API Gateway │ │ API Server │ │ │ │ │ │ │ │ + Token │─────────▶│ Validates │─────────▶ │ Processes │ │ │ │ Token │ │ Request │ └─────────────┘ └──────────────┘ └─────────────┘ ↑ │ │ │ └───────────────────────────┘ Returns Response

Obtaining API Tokens

  1. Log into your account dashboard
  2. Navigate to Settings → API Keys
  3. Generate a new token with appropriate scopes
  4. Store your token securely (never commit to version control)

Authorization Header

Include your token in every request:

# Using curl curl -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ https://api.example.com/v1/items

Token Scopes

Tokens support granular permissions to control access levels:

ScopePermissionsUse Case
items:readRead items and metadataRead-only integrations, analytics
items:writeCreate, update, delete itemsFull management applications
webhooks:manageConfigure webhook endpointsEvent-driven architectures
admin:allComplete account accessAdministrative tools

Security Best Practice: Always use the minimum required scope for your application. Rotate tokens regularly and revoke unused tokens immediately.


Endpoints

The API provides CRUD operations for managing items through intuitive REST endpoints. All endpoints accept and return JSON unless otherwise specified.

HTTP Method Endpoint Action ─────────────────────────────────────────────────────── GET /items List all items POST /items Create new item GET /items/\{id\} Get single item PATCH /items/\{id\} Update item DELETE /items/\{id\} Delete item

GET /items

List Items

Retrieve a paginated list of all items accessible to your account. Supports filtering, sorting, and cursor-based pagination for efficient data retrieval.

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoMaximum results per page (default: 20, max: 100)
cursorstringNoPagination cursor from previous response
qstringNoSearch query for filtering items by name
sortstringNoSort field: name, created_at, updated_at
orderstringNoSort order: asc or desc (default: asc)
statusstringNoFilter by status: active, archived, draft

Request Example

curl -X GET "https://api.example.com/v1/items?limit=10&sort=created_at&order=desc" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json"

Response Example

{ "data": [ { "id": "it_123", "name": "Alpha Product", "status": "active", "created_at": "2024-12-01T10:30:00Z", "updated_at": "2024-12-15T14:22:00Z" }, { "id": "it_124", "name": "Beta Service", "status": "active", "created_at": "2024-12-05T08:15:00Z", "updated_at": "2024-12-10T16:45:00Z" } ], "meta": { "count": 2, "next_cursor": "eyJvZmZzZXQiOjEwfQ" } }

Response Status Codes

  • 200 OK - Items retrieved successfully
  • 400 Bad Request - Invalid query parameters
  • 401 Unauthorized - Missing or invalid authentication
  • 429 Too Many Requests - Rate limit exceeded

POST /items

Create New Item

Create a new item with specified attributes. Returns the created item with generated ID and timestamps.

Required Scope

items:write

Request Body

FieldTypeRequiredDescription
namestringYesItem name (3-100 characters)
descriptionstringNoDetailed description (max 1000 characters)
statusstringNoInitial status: draft (default), active
metadataobjectNoCustom key-value pairs

Request Example

curl -X POST "https://api.example.com/v1/items" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "New Product Launch", "description": "Q1 2025 flagship product", "status": "draft", "metadata": { "category": "electronics", "priority": "high" } }'

Response Example

{ "data": { "id": "it_125", "name": "New Product Launch", "description": "Q1 2025 flagship product", "status": "draft", "metadata": { "category": "electronics", "priority": "high" }, "created_at": "2024-12-17T09:00:00Z", "updated_at": "2024-12-17T09:00:00Z" } }

Response Status Codes

  • 201 Created - Item created successfully
  • 400 Bad Request - Validation error in request body
  • 401 Unauthorized - Missing or invalid authentication
  • 403 Forbidden - Insufficient permissions (missing items:write)
  • 422 Unprocessable Entity - Semantic validation error

GET /items/{id}

Retrieve Single Item

Fetch detailed information for a specific item by its unique identifier.

Path Parameters

ParameterTypeRequiredDescription
idstringYesUnique item identifier (e.g., it_123)

Request Example

curl -X GET "https://api.example.com/v1/items/it_123" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json"

Response Example

{ "data": { "id": "it_123", "name": "Alpha Product", "description": "Premium product line", "status": "active", "metadata": { "category": "electronics", "sku": "APH-001" }, "created_at": "2024-12-01T10:30:00Z", "updated_at": "2024-12-15T14:22:00Z" } }

Response Status Codes

  • 200 OK - Item retrieved successfully
  • 401 Unauthorized - Missing or invalid authentication
  • 404 Not Found - Item does not exist or is inaccessible

PATCH /items/{id}

Update Item

Partially update an existing item. Only provided fields will be modified; omitted fields remain unchanged.

Required Scope

items:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesUnique item identifier

Request Body

Any combination of updateable fields from the create endpoint. All fields are optional.

Request Example

curl -X PATCH "https://api.example.com/v1/items/it_123" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "status": "active", "metadata": { "featured": true } }'

Response Example

{ "data": { "id": "it_123", "name": "Alpha Product", "description": "Premium product line", "status": "active", "metadata": { "category": "electronics", "sku": "APH-001", "featured": true }, "created_at": "2024-12-01T10:30:00Z", "updated_at": "2024-12-17T09:15:00Z" } }

Response Status Codes

  • 200 OK - Item updated successfully
  • 400 Bad Request - Invalid update data
  • 401 Unauthorized - Missing or invalid authentication
  • 403 Forbidden - Insufficient permissions
  • 404 Not Found - Item does not exist
  • 422 Unprocessable Entity - Validation error

DELETE /items/{id}

Delete Item

Permanently remove an item from your account. This action cannot be undone.

Required Scope

items:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesUnique item identifier

Request Example

curl -X DELETE "https://api.example.com/v1/items/it_123" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json"

Response Example

{ "data": { "id": "it_123", "deleted": true, "deleted_at": "2024-12-17T09:30:00Z" } }

Response Status Codes

  • 200 OK - Item deleted successfully
  • 401 Unauthorized - Missing or invalid authentication
  • 403 Forbidden - Insufficient permissions
  • 404 Not Found - Item does not exist or already deleted

Pagination

The API uses cursor-based pagination for efficient navigation through large datasets. This approach provides consistent results even when data changes between requests.

Request 1 Request 2 Request 3 ────────────────────────────────────────────────────────────── ?limit=25 → ?limit=25 → ?limit=25 &cursor=ABC123 &cursor=XYZ789 ┌────────────┐ ┌────────────┐ ┌────────────┐ │ Items 1-25 │ │ Items 26-50│ │ Items 51-75│ │ │ │ │ │ │ │ cursor: │ │ cursor: │ │ cursor: │ │ ABC123 │ │ XYZ789 │ │ null │ └────────────┘ └────────────┘ └────────────┘

How Cursor Pagination Works

Instead of page numbers or offsets, each response includes an opaque next_cursor token that points to the next set of results. Cursors are time-limited and typically valid for 24 hours.

Pagination Parameters

ParameterTypeDescription
limitintegerResults per page (default: 20, max: 100)
cursorstringCursor token from previous response

Example Workflow

Initial Request:

curl "https://api.example.com/v1/items?limit=25"

Response with Cursor:

{ "data": [...], "meta": { "count": 25, "next_cursor": "eyJvZmZzZXQiOjI1fQ", "has_more": true } }

Next Page Request:

curl "https://api.example.com/v1/items?limit=25&cursor=eyJvZmZzZXQiOjI1fQ"

Best Practices

  • Always check the has_more field to determine if additional pages exist
  • Store cursors temporarily; they expire after 24 hours
  • Use consistent limit values throughout pagination
  • Never attempt to manually construct or decode cursor tokens

Filtering & Sorting

Refine query results using flexible filtering and multi-field sorting capabilities.

Filtering

Apply filters using query parameters that correspond to item fields:

curl "https://api.example.com/v1/items?status=active&q=electronics" \ -H "Authorization: Bearer YOUR_API_TOKEN"

Available Filters

FilterTypeExampleDescription
qstringq=smartphoneFull-text search across name and description
statusenumstatus=activeFilter by item status
created_afterISO 8601created_after=2024-12-01Items created after date
created_beforeISO 8601created_before=2024-12-31Items created before date

Sorting

Sort results by any supported field using sort and order parameters:

curl "https://api.example.com/v1/items?sort=created_at&order=desc" \ -H "Authorization: Bearer YOUR_API_TOKEN"

Sortable Fields

  • name - Alphabetical by name
  • created_at - Creation timestamp
  • updated_at - Last modification timestamp
  • status - Status value (alphabetical)

Complex Query Example

curl "https://api.example.com/v1/items?status=active&sort=updated_at&order=desc&limit=50&q=premium" \ -H "Authorization: Bearer YOUR_API_TOKEN"

This retrieves the 50 most recently updated active items matching “premium”, sorted newest first.


Rate Limits

To ensure fair usage and system stability, the API enforces rate limits on all requests. Limits are applied per API token.

Rate Limit Window (1 minute) ──────────────────────────────────────────────────── 0s 30s 60s ├──────────────────────┼──────────────────────┤ │ ████████████████████ │ ░░░░░░░░░░░░░░░░░░░░ │ │ 60 requests used │ 0 requests left │ └──────────────────────┴──────────────────────┘ Reset at 60s

Rate Limit Tiers

TierRequests/MinuteRequests/Hour
Free601,000
Pro30010,000
Enterprise1,00050,000

Rate Limit Headers

Every response includes headers showing your current rate limit status:

# Example response headers X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 847 X-RateLimit-Reset: 1702814400
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in current window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the limit resets

Handling Rate Limits

When you exceed your rate limit, the API returns a 429 Too Many Requests response:

{ "error": { "type": "rate_limit_exceeded", "message": "Rate limit exceeded. Retry after 45 seconds.", "retry_after": 45 } }

Best Practices

  • Monitor X-RateLimit-Remaining header values
  • Implement exponential backoff when receiving 429 responses
  • Cache frequently accessed data to reduce API calls
  • Use webhooks instead of polling for real-time updates
  • Consider upgrading your plan if consistently hitting limits

Errors

The API uses conventional HTTP status codes and returns detailed error objects to help diagnose issues quickly.

Error Flow ────────────────────────────────────────────────────── Client Request ┌─────────────┐ │ Validation │──── Pass ────▶ Process Request │ │ └─────────────┘ │ Fail ┌─────────────┐ │ Error │ │ Response │ │ + Details │ └─────────────┘

HTTP Status Code Summary

CodeMeaningWhen It Occurs
200 OKSuccessRequest completed successfully
201 CreatedResource createdPOST request succeeded
400 Bad RequestInvalid requestMalformed JSON, missing required fields
401 UnauthorizedAuthentication failedMissing, invalid, or expired token
403 ForbiddenPermission deniedValid token but insufficient permissions
404 Not FoundResource not foundRequested item doesn’t exist
422 Unprocessable EntityValidation errorRequest valid but semantically incorrect
429 Too Many RequestsRate limit exceededToo many requests in time window
500 Internal Server ErrorServer errorUnexpected server-side issue
503 Service UnavailableMaintenance modeAPI temporarily unavailable

Error Response Format

All errors return a consistent JSON structure:

{ "error": { "type": "validation_error", "message": "The provided data is invalid.", "code": "INVALID_REQUEST", "details": [ { "field": "name", "message": "Name must be between 3 and 100 characters.", "code": "length_out_of_range" } ], "request_id": "req_abc123xyz" } }

Error Object Fields

FieldTypeDescription
typestringError category for programmatic handling
messagestringHuman-readable error description
codestringMachine-readable error code
detailsarrayField-level validation errors (when applicable)
request_idstringUnique request identifier for support

Common Error Types

validation_error - Request data failed validation

{ "error": { "type": "validation_error", "message": "Invalid request parameters.", "code": "VALIDATION_FAILED" } }

authentication_error - Authentication credentials invalid

{ "error": { "type": "authentication_error", "message": "Invalid or expired API token.", "code": "INVALID_TOKEN" } }

resource_not_found - Requested resource doesn’t exist

{ "error": { "type": "resource_not_found", "message": "The requested item could not be found.", "code": "NOT_FOUND" } }

rate_limit_exceeded - Too many requests

{ "error": { "type": "rate_limit_exceeded", "message": "Rate limit exceeded. Retry after 60 seconds.", "code": "RATE_LIMIT", "retry_after": 60 } }

Error Handling Best Practices

  1. Always check HTTP status codes before parsing response bodies
  2. Log request_id values when contacting support
  3. Parse details arrays for field-specific validation errors
  4. Implement retry logic with exponential backoff for 5xx errors
  5. Never expose raw error messages to end users; provide user-friendly alternatives

Webhooks

Receive real-time notifications when events occur in your account. Webhooks eliminate the need for polling and enable event-driven architectures.

Webhook Flow ──────────────────────────────────────────────────── Event Occurs ┌─────────────┐ │ API Server │ │ │ │ Triggers │ │ Webhook │ └─────────────┘ ┌─────────────┐ ┌─────────────┐ │ POST │─────────▶│ Your Server │ │ Request │ │ │ │ │◀─────────│ 200 OK │ └─────────────┘ └─────────────┘

How Webhooks Work

When a subscribed event occurs, the API sends an HTTP POST request to your configured webhook URL with event details in the request body.

Configuring Webhooks

  1. Navigate to Settings → Webhooks in your dashboard
  2. Click “Add Webhook Endpoint”
  3. Enter your HTTPS endpoint URL
  4. Select events to subscribe to
  5. Save and note your webhook signing secret

Webhook Events

EventTrigger
item.createdNew item created
item.updatedItem modified
item.deletedItem deleted
item.status_changedItem status changed

Webhook Payload Format

{ "id": "evt_abc123", "type": "item.created", "created_at": "2024-12-17T10:00:00Z", "data": { "id": "it_126", "name": "New Item", "status": "active", "created_at": "2024-12-17T10:00:00Z" } }

Verifying Webhook Signatures

Every webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 signature. Always verify this signature to ensure the request originated from our API.

Verification Example (Node.js):

const crypto = require('crypto'); function verifyWebhook(payload, signature, secret) { const hmac = crypto.createHmac('sha256', secret); const digest = hmac.update(payload).digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(digest) ); }

Webhook Best Practices

  • Respond with 200 OK within 5 seconds to acknowledge receipt
  • Process events asynchronously to avoid timeouts
  • Implement idempotency using the id field to handle duplicate deliveries
  • Store webhook secrets securely (environment variables, secret managers)
  • Return 2xx status codes only after successful processing
  • Failed webhooks are retried up to 3 times with exponential backoff

SDK Integration

Get started quickly with our lightweight SDK for popular programming languages.

JavaScript / Node.js

const API_BASE = 'https://api.example.com/v1'; const TOKEN = 'YOUR_API_TOKEN'; const api = { async request(endpoint, options = {}) { const res = await fetch(`$\{API_BASE\}$\{endpoint\}`, { ...options, headers: { 'Authorization': `Bearer $\{TOKEN\}`, 'Content-Type': 'application/json', ...options.headers } }); if (!res.ok) throw new Error(await res.text()); return res.json(); }, get: (endpoint) => api.request(endpoint), post: (endpoint, data) => api.request(endpoint, { method: 'POST', body: JSON.stringify(data) }), patch: (endpoint, data) => api.request(endpoint, { method: 'PATCH', body: JSON.stringify(data) }), delete: (endpoint) => api.request(endpoint, { method: 'DELETE' }) }; // Usage const items = await api.get('/items?limit=10'); const newItem = await api.post('/items', { name: 'Test', status: 'active' }); await api.patch(`/items/${newItem.data.id}`, { status: 'archived' }); await api.delete(`/items/${newItem.data.id}`);

Python

import requests class API: def __init__(self, token): self.base = 'https://api.example.com/v1' self.headers = { 'Authorization': f'Bearer \{token\}', 'Content-Type': 'application/json' } def get(self, path, params=None): return requests.get(f'{self.base}\{path\}', headers=self.headers, params=params).json() def post(self, path, data): return requests.post(f'{self.base}\{path\}', headers=self.headers, json=data).json() def patch(self, path, data): return requests.patch(f'{self.base}\{path\}', headers=self.headers, json=data).json() def delete(self, path): return requests.delete(f'{self.base}\{path\}', headers=self.headers).json() # Usage api = API('YOUR_API_TOKEN') items = api.get('/items', {'limit': 10}) new_item = api.post('/items', {'name': 'Test', 'status': 'active'}) api.patch(f'/items/{new_item["data"]["id"]}', {'status': 'archived'}) api.delete(f'/items/{new_item["data"]["id"]}')

PHP

<?php class API { private $base = 'https://api.example.com/v1'; private $token; public function __construct($token) { $this->token = $token; } private function request($method, $path, $data = null) { $ch = curl_init($this->base . $path); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $this->token, 'Content-Type: application/json' ], CURLOPT_POSTFIELDS => $data ? json_encode($data) : null ]); $response = curl_exec($ch); curl_close($ch); return json_decode($response, true); } public function get($path, $params = []) { $query = http_build_query($params); return $this->request('GET', $path . ($query ? '?' . $query : '')); } public function post($path, $data) { return $this->request('POST', $path, $data); } public function patch($path, $data) { return $this->request('PATCH', $path, $data); } public function delete($path) { return $this->request('DELETE', $path); } } // Usage $api = new API('YOUR_API_TOKEN'); $items = $api->get('/items', ['limit' => 10]); $newItem = $api->post('/items', ['name' => 'Test', 'status' => 'active']); $api->patch('/items/' . $newItem['data']['id'], ['status' => 'archived']); $api->delete('/items/' . $newItem['data']['id']); ?>

Ruby

require 'net/http' require 'json' class API BASE = 'https://api.example.com/v1' def initialize(token) @token = token end def request(method, path, data = nil) uri = URI("#\{BASE\}#\{path\}") req = Net::HTTP.const_get(method).new(uri) req['Authorization'] = "Bearer #{@token}" req['Content-Type'] = 'application/json' req.body = data.to_json if data res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(req) end JSON.parse(res.body) end def get(path, params = {}) uri = URI("#\{BASE\}#\{path\}") uri.query = URI.encode_www_form(params) unless params.empty? request('Get', uri.request_uri) end def post(path, data) request('Post', path, data) end def patch(path, data) request('Patch', path, data) end def delete(path) request('Delete', path) end end # Usage api = API.new('YOUR_API_TOKEN') items = api.get('/items', limit: 10) new_item = api.post('/items', { name: 'Test', status: 'active' }) api.patch("/items/#{new_item['data']['id']}", { status: 'archived' }) api.delete("/items/#{new_item['data']['id']}")