# Developer Guide: [Product name] API and SDKs

> Complete guide for developers who integrate with an API or build on an
> SDK: concepts, setup, authentication, working code, error handling,
> limits, webhooks, testing, versioning and support. Based on common
> industry practice for developer documentation. Replace every
> `[bracketed]` value and every row marked *Example*.

<!--
Guidance: How to use this template
- Optimise for time to first successful call. A developer should get a
  working response in under ten minutes from section 4.
- Every code sample must run as written after the reader replaces the
  placeholders. Test samples in CI if you can.
- Keep the endpoint-by-endpoint reference in a separate API reference
  (often generated from OpenAPI). This guide explains how to use it well.
- Show the same example in the languages your users actually use; two or
  three well-maintained languages beat six stale ones.
-->

## Document control

<!-- Guidance: Identify which API version and SDK versions the guide describes. Developers often land on an old page from a search engine. -->

| Field | Value |
|---|---|
| Document ID | [DG-PRODUCT-001] |
| API version | [v2 (2026-09-01)] |
| SDK versions | [Python 4.1, JavaScript 5.0, Go 1.8] |
| Guide version | [0.1] |
| Status | [Draft / In review / Approved / Superseded] |
| Owner | [@devrel-owner] |
| Approvers | [@api-lead], [@security-reviewer] |
| Last updated | YYYY-MM-DD |

### Approvals

| Role | Name | Decision | Date |
|---|---|---|---|
| API product owner | [@name] | [Approved / Rejected / Approved with comments] | YYYY-MM-DD |
| SDK maintainer | [@name] | [Decision] | YYYY-MM-DD |
| Security reviewer | [@name] | [Decision] | YYYY-MM-DD |

## Revision history

<!-- Guidance: One row per published version. Link to the API changelog for detailed changes. -->

| Version | Date | Author | Change summary |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@name] | Initial draft |
| 1.0 | YYYY-MM-DD | [@name] | *Example:* Published with API v2 general availability |
| 1.1 | YYYY-MM-DD | [@name] | *Example:* Added Go samples and webhook signature verification |

## Contents

1. [Overview](#1-overview)
2. [Concepts](#2-concepts)
3. [Setup](#3-setup)
4. [Quick example](#4-quick-example)
5. [Authentication](#5-authentication)
6. [Core tasks](#6-core-tasks)
7. [Error handling](#7-error-handling)
8. [Pagination](#8-pagination)
9. [Rate limits](#9-rate-limits)
10. [Webhooks](#10-webhooks)
11. [Testing and sandbox](#11-testing-and-sandbox)
12. [SDK reference conventions](#12-sdk-reference-conventions)
13. [Versioning and deprecation](#13-versioning-and-deprecation)
14. [Migration notes](#14-migration-notes)
15. [Best practices](#15-best-practices)
16. [Support](#16-support)
17. [Appendix A. Glossary](#appendix-a-glossary)

## 1. Overview

<!-- Guidance: In one paragraph say what developers can build and what they cannot. Then show the main pieces and how they fit. -->

The [Product name] API lets you [create and manage invoices, collect
payments and receive status updates] from your own application. Official
SDKs wrap the REST API for [Python, JavaScript and Go]; any language that
can send HTTPS requests and parse JSON can use the API directly.

| You can | You cannot |
|---|---|
| *Example:* Create, update and send invoices | Change account billing settings (dashboard only) |
| *Example:* Receive webhook events for payment status | Access other accounts' data |
| [Capability] | [Limitation] |

```mermaid
sequenceDiagram
    participant App as Your app
    participant API as Product API
    participant WH as Your webhook endpoint
    App->>API: POST /v2/invoices
    API-->>App: 201 Created (invoice id)
    API->>WH: invoice.paid event
    WH-->>API: 200 OK
```

## 2. Concepts

<!-- Guidance: Define the objects and rules a developer must understand before reading code. Keep it short and link to the explanation docs for depth. -->

| Concept | Meaning | Example |
|---|---|---|
| Resource | An object with a stable ID and URL | `inv_8f2c...` at `/v2/invoices/inv_8f2c...` |
| Idempotency key | A client-chosen key that makes a retried request safe | `Idempotency-Key: 6c1e-...` |
| Environment | Separate data and keys for testing and live use | `sandbox`, `live` |
| Event | A record that something happened, delivered by webhook | `invoice.paid` |
| Expansion | Inline related objects in a response | `?expand=customer` |

The main objects relate as follows. Use this model to decide which IDs to
store in your own database.

```mermaid
erDiagram
    CUSTOMER ||--o{ INVOICE : receives
    INVOICE ||--|{ LINE_ITEM : contains
    INVOICE ||--o{ PAYMENT : "is settled by"
    INVOICE ||--o{ EVENT : emits
```

All timestamps are ISO 8601 in UTC. Monetary amounts are integers in the
smallest currency unit (for example cents) with an ISO 4217 currency code.

## 3. Setup

<!-- Guidance: List exactly what to install and create, in order. Include the version constraints that actually matter. -->

1. Create a developer account at [https://dashboard.example.com/signup].
2. In **Developers > API keys**, create a **sandbox** secret key.
3. Store it in an environment variable, never in source code:

   ```bash
   export EXAMPLE_API_KEY="sk_sandbox_[...]"
   ```

4. Install an SDK:

   | Language | Requirement | Install |
   |---|---|---|
   | Python | 3.10+ | `pip install example-sdk==4.1.*` |
   | JavaScript / TypeScript | Node.js 20+ | `npm install @example/sdk@5` |
   | Go | 1.22+ | `go get github.com/example/sdk-go@v1.8` |

### 3.1 Environments and base URLs

| Environment | Base URL | Keys start with | Data |
|---|---|---|---|
| Sandbox | `https://api.sandbox.example.com/v2` | `sk_sandbox_` | Test data only; reset on request |
| Live | `https://api.example.com/v2` | `sk_live_` | Real customers and money |

The SDKs pick the base URL from the key prefix, so switching environments
means switching keys. Keep separate keys per environment and per service
so you can revoke one without affecting the others.

### 3.2 Recommended project setup

- Read the key from configuration at start-up and fail fast if it is missing.
- Create one client per process and reuse it; it keeps connections open.
- Turn on SDK debug logging only in development: it prints request paths
  and status codes but never the key.

## 4. Quick example

<!-- Guidance: One complete, copy-paste example per language that creates something visible. Show the expected output. -->

Create a customer and an invoice, then print the invoice URL.

```python
import os
from example_sdk import Client

client = Client(api_key=os.environ["EXAMPLE_API_KEY"])
customer = client.customers.create(email="ana@example.org", name="Ana Silva")
invoice = client.invoices.create(
    customer=customer.id,
    currency="EUR",
    lines=[{"description": "Consulting, March", "amount": 120000}],
)
print(invoice.hosted_url)
```

```javascript
import { Client } from "@example/sdk";

const client = new Client({ apiKey: process.env.EXAMPLE_API_KEY });
const customer = await client.customers.create({ email: "ana@example.org", name: "Ana Silva" });
const invoice = await client.invoices.create({
  customer: customer.id,
  currency: "EUR",
  lines: [{ description: "Consulting, March", amount: 120000 }],
});
console.log(invoice.hostedUrl);
```

```bash
curl https://api.example.com/v2/invoices \
  -H "Authorization: Bearer $EXAMPLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customer":"cus_123","currency":"EUR","lines":[{"description":"Consulting, March","amount":120000}]}'
```

Expected output: a URL such as `https://pay.example.com/i/inv_8f2c...`.
Open it to see the invoice in the sandbox.

## 5. Authentication

<!-- Guidance: Explain each supported method, when to use it, how to keep credentials safe, and what an auth failure looks like. -->

| Method | Use when | Header |
|---|---|---|
| Secret API key | Server-to-server calls from your backend | `Authorization: Bearer sk_live_...` |
| OAuth 2.0 authorization code with PKCE | Acting on behalf of another account | `Authorization: Bearer <access token>` |
| Restricted key | A service needs only some permissions | Same as secret key |

Rules for keys:

- Never ship a secret key in a mobile app or browser code.
- Use restricted keys with the minimum scopes, for example `invoices:write`.
- Rotate keys every [90] days and immediately if exposed; old keys can
  overlap with new ones for [24] hours.

For OAuth, redirect the account holder to
`https://connect.example.com/oauth/authorize` with your `client_id`, the
requested `scope`, a random `state` and a PKCE `code_challenge`. After
consent, exchange the returned `code` at `POST /oauth/token` for an access
token (valid [1 hour]) and a refresh token (valid [90 days], rotated on
each use). Store refresh tokens encrypted and revoke them when the
connected account disconnects your app.

A missing or invalid key returns `401` with error code `authentication_failed`.

## 6. Core tasks

<!-- Guidance: Show the three to six things most integrations do, each with a short explanation and code. Link to the reference for every parameter. -->

### 6.1 Retrieve and update a resource

Retrieve an object by its ID, optionally expanding related objects, then
send only the fields you want to change. Fields you omit keep their value;
send `null` to clear an optional field.

```python
invoice = client.invoices.retrieve("inv_8f2c", expand=["customer"])
client.invoices.update(invoice.id, due_date="2026-11-30")
```

### 6.2 Make a write request safe to retry

Send an idempotency key with every `POST`. If the network fails, retry
with the same key; the API returns the original result instead of
creating a duplicate.

```javascript
await client.invoices.create(params, { idempotencyKey: crypto.randomUUID() });
```

### 6.3 Filter and expand

`GET /v2/invoices?status=open&created[gte]=2026-10-01&expand=customer`
returns open invoices created since 1 October with the customer inline.

### 6.4 Attach your own data with metadata

Every resource accepts up to [50] `metadata` key-value pairs (keys up to 40
characters, values up to 500). Use metadata to store your own order ID so
you can match webhook events to your records without an extra lookup.
Do not store personal or sensitive data in metadata; it appears in
exports and in the dashboard.

```python
client.invoices.update("inv_8f2c", metadata={"order_id": "ORD-2026-00417"})
```

### 6.5 Search across resources

`GET /v2/search/invoices?query=metadata['order_id']:'ORD-2026-00417'`
returns matching invoices. Search results can lag writes by up to
[60 seconds], so use direct retrieval when you need the latest state.

## 7. Error handling

<!-- Guidance: Document the error format once, list the codes developers must handle, and say which errors are safe to retry. -->

Errors use HTTP status codes and a JSON body:

```json
{
  "error": {
    "type": "invalid_request",
    "code": "parameter_missing",
    "message": "The 'currency' parameter is required.",
    "param": "currency",
    "request_id": "req_91ab..."
  }
}
```

| HTTP status | Type | Meaning | Retry? |
|---|---|---|---|
| 400 | `invalid_request` | Missing or invalid parameter | No; fix the request |
| 401 | `authentication_failed` | Missing, invalid or revoked key | No |
| 403 | `permission_denied` | Key lacks the scope | No |
| 404 | `not_found` | Resource does not exist in this environment | No |
| 409 | `conflict` | Idempotency key reused with different parameters | No |
| 429 | `rate_limited` | Too many requests | Yes, after `Retry-After` |
| 500, 502, 503 | `api_error` | Temporary problem on our side | Yes, with backoff |

Retry with exponential backoff and jitter: wait
$t_n = \min(t_{max},\ t_0 \cdot 2^n) \cdot U(0.5, 1)$ seconds before attempt
$n$, with $t_0 = 0.5$ and $t_{max} = 30$. Always log `request_id`.

## 8. Pagination

<!-- Guidance: Explain the pagination model and show how to iterate all results. Cursor pagination is safer than offsets for changing data. -->

List endpoints return up to `limit` items (default 20, maximum 100) and
a cursor:

```json
{ "data": [ ... ], "has_more": true, "next_cursor": "cur_3Jd..." }
```

Pass `starting_after=<next_cursor>` to get the next page. The SDKs
iterate automatically:

```python
for invoice in client.invoices.list(status="open").auto_paging_iter():
    process(invoice)
```

## 9. Rate limits

<!-- Guidance: State the limits, the headers that report them, and how to stay below them. -->

| Environment | Limit | Burst |
|---|---|---|
| Sandbox | 25 requests per second per account | 50 |
| Live | 100 requests per second per account | 200 |

Every response includes `RateLimit-Limit`, `RateLimit-Remaining` and
`RateLimit-Reset`. On `429`, wait for the `Retry-After` seconds. Use
webhooks instead of polling to stay well under the limit.

## 10. Webhooks

<!-- Guidance: Cover subscription, payload, signature verification, retries and ordering. Unverified webhooks are a security hole. -->

1. Register an HTTPS endpoint in **Developers > Webhooks** and choose events.
2. Respond with `2xx` within 10 seconds; process work asynchronously.
3. Verify the signature before trusting the payload:

```python
from example_sdk.webhooks import verify

event = verify(payload=request.body,
               header=request.headers["Example-Signature"],
               secret=os.environ["EXAMPLE_WEBHOOK_SECRET"],
               tolerance_seconds=300)
```

Failed deliveries are retried for [3 days] with increasing delay. Events
may arrive out of order or more than once; deduplicate by `event.id` and
check the resource's current state before acting.

| Event | Sent when |
|---|---|
| `invoice.finalized` | An invoice can no longer be edited |
| `invoice.paid` | Payment succeeded |
| `invoice.payment_failed` | A payment attempt failed |
| [event] | [Trigger] |

## 11. Testing and sandbox

<!-- Guidance: Explain how to test without real money or data, including special test values and how to trigger webhooks locally. -->

The sandbox is fully isolated from live data. Use these test values:

| Value | Behaviour |
|---|---|
| Card `4242 4242 4242 4242` | Payment succeeds |
| Card `4000 0000 0000 0002` | Payment is declined |
| Email ending `+fail@example.org` | Invoice email bounces |

Forward webhooks to your laptop with `example-cli listen --forward-to
localhost:3000/webhooks`, and trigger events with `example-cli trigger
invoice.paid`.

### 11.1 Go-live checklist

| # | Check | Done |
|---|---|---|
| 1 | Live keys stored in the secret manager, not in code or CI logs | [ ] |
| 2 | Every write request sends an idempotency key | [ ] |
| 3 | Webhook signatures are verified and events deduplicated by ID | [ ] |
| 4 | `429` and `5xx` are retried with backoff; other errors are logged with `request_id` | [ ] |
| 5 | API version pinned in configuration | [ ] |
| 6 | Alerting on webhook delivery failures and error rate | [ ] |
| 7 | Tested against the sandbox scenarios in this section | [ ] |

## 12. SDK reference conventions

<!-- Guidance: Tell readers how SDK names map to the HTTP API so they can use the REST reference with any SDK. -->

| HTTP | Python | JavaScript | Go |
|---|---|---|---|
| `POST /v2/invoices` | `client.invoices.create()` | `client.invoices.create()` | `client.Invoices.Create()` |
| `GET /v2/invoices/{id}` | `client.invoices.retrieve(id)` | `client.invoices.retrieve(id)` | `client.Invoices.Get(id)` |
| Field `hosted_url` | `hosted_url` | `hostedUrl` | `HostedURL` |

SDK errors raise or return typed exceptions named after the error type,
for example `RateLimitedError`. Each SDK follows semantic versioning.

## 13. Versioning and deprecation

<!-- Guidance: State what counts as a breaking change, how versions are selected, and how much notice you give. -->

The API version is a date sent in the `Example-Version` header; your
account default is set when you create it. Additive changes (new fields,
new endpoints, new event types) can happen at any time, so parse JSON
tolerantly. Breaking changes ship only in a new version, announced at
least [12 months] before the old version is retired.

| API version | Released | Status | Retirement date |
|---|---|---|---|
| *Example:* 2026-09-01 (v2) | 2026-09-01 | Current | None planned |
| *Example:* 2024-03-15 (v1) | 2024-03-15 | Deprecated | 2027-09-30 |
| [Version] | YYYY-MM-DD | [Current / Deprecated / Retired] | YYYY-MM-DD |

Calls to a deprecated version return a `Deprecation` header with the
retirement date, so you can detect old integrations from your own logs.

## 14. Migration notes

<!-- Guidance: Summarise what changes for developers moving between major versions and link to the full migration guide. -->

| v1 | v2 | Action |
|---|---|---|
| *Example:* `amount` as decimal string | `amount` as integer in minor units | Multiply by 100 for EUR, USD |
| *Example:* Offset pagination (`page`) | Cursor pagination (`starting_after`) | Use SDK auto-paging |
| *Example:* Unsigned webhooks | Signed webhooks | Add signature verification |

Full details: [Migration Guide v1 to v2].

## 15. Best practices

<!-- Guidance: Short, testable recommendations drawn from real support cases. -->

- Keep keys in a secret manager and use restricted keys per service.
- Send an idempotency key with every write request.
- Handle `429` and `5xx` with backoff; never retry `4xx` other than `429`.
- Use webhooks for state changes instead of polling.
- Store our resource IDs in your database rather than matching on names.
- Pin the API version and SDK major version; upgrade deliberately.
- Set client timeouts (for example 10 s connect, 30 s read) so a slow
  network does not block your workers indefinitely.
- Log request IDs, not request bodies, to avoid storing personal data
  and secrets in your logs.
- Subscribe to the changelog feed so deprecations never surprise you.

## 16. Support

<!-- Guidance: Tell developers where to go and what to include. -->

| Need | Where |
|---|---|
| API status and incidents | [https://status.example.com] |
| Questions and examples | [Developer forum URL] |
| Bugs in an SDK | Issue tracker in the SDK repository |
| Account or production issues | [developer-support@example.com] |

Include the `request_id`, API version, SDK name and version, and a
minimal code sample. Never send secret keys.

## Appendix A. Glossary

<!-- Guidance: Define terms specific to this API. Keep definitions consistent with the reference. -->

| Term | Meaning |
|---|---|
| Minor unit | Smallest unit of a currency, for example cents for EUR |
| Idempotency | Property that repeating a request has the same effect as sending it once |
| Scope | A named permission attached to a restricted key or OAuth token |
| Cursor | Opaque value that marks a position in a list for pagination |
| [Term] | [Meaning] |

## Standard and licence note

Based on common industry practice for API and SDK developer
documentation (guides that combine concepts, quick starts, task examples
and operational rules, with a separate generated API reference). This
template is original text written for this collection. Template licence: MIT.
