# API Specification

# API Specification

> **Project:** {{PROJECT_NAME}}
> **API Name:** {{API_NAME}}
> **Version:** {{API_VERSION}}
> **Date:** {{DATE}}
> **Author:** {{AUTHOR}}
> **Status:** Draft | In Review | Approved
> **Reviewers:** {{REVIEWERS}}
> **Spec Format:** OpenAPI 3.1

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1     | {{DATE}} | {{AUTHOR}} | Initial draft |
| {{VERSION}} | {{DATE}} | {{AUTHOR}} | {{CHANGE_SUMMARY}} |

---

## 1. API Overview

<!-- GUIDANCE: Describe what this API does, who the consumers are, and the core design philosophy. -->

**Purpose:** {{API_PURPOSE}}
**Primary Consumers:** {{CONSUMER_DESCRIPTION}} (e.g., internal frontend, partner systems, public developers)
**Design Philosophy:** REST + JSON | Resource-oriented | Stateless | Idempotent where possible

**Base URLs:**

| Environment | Base URL |
|-------------|----------|
| Production | `https://api.{{DOMAIN}}/v{{MAJOR_VERSION}}` |
| Staging | `https://api.staging.{{DOMAIN}}/v{{MAJOR_VERSION}}` |
| Development | `http://localhost:{{PORT}}/api/v{{MAJOR_VERSION}}` |

---

## 2. API Versioning Strategy

<!-- GUIDANCE: Define how breaking changes are handled. Consumers must be notified before breaking changes. -->

**Strategy:** URL path versioning — `/api/v{MAJOR}`

**Versioning rules:**
- **MAJOR** version (v1 → v2): Breaking changes — new base path, deprecation notice ≥ 6 months
- **MINOR** additions: Non-breaking — new optional fields, new endpoints — no version bump
- **Patch**: Bug fixes — no schema changes

**Deprecation Policy:**
- Deprecated endpoints marked with `Deprecation` and `Sunset` headers
- Minimum {{DEPRECATION_PERIOD}} notice before removing deprecated endpoints
- Deprecation notices sent to: {{NOTIFICATION_CHANNEL}}

**Sunset Header Example:**
```http
Deprecation: Sat, 01 Jan 2025 00:00:00 GMT
Sunset: Sat, 01 Jul 2025 00:00:00 GMT
Link: <https://api.{{DOMAIN}}/v2/{{resource}}>; rel="successor-version"
```

---

## 3. Authentication & Authorization

<!-- GUIDANCE: Define every auth mechanism supported. Include token lifetime, refresh strategy, and scope definitions. -->

### 3.1 Authentication Methods

**Primary:** Bearer JWT (OAuth2 / OIDC)

```http
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
```

**JWT Claims:**
```json
{
  "sub": "user-uuid",
  "email": "user@example.com",
  "tenant_id": "tenant-uuid",
  "roles": ["{{ROLE_1}}", "{{ROLE_2}}"],
  "scopes": ["{{SCOPE_1}}:read", "{{SCOPE_1}}:write"],
  "iat": 1700000000,
  "exp": 1700003600
}
```

**Token Lifetimes:**
| Token Type | Lifetime | Storage |
|-----------|---------|---------|
| Access Token | {{ACCESS_TTL}} (e.g., 1h) | Memory only (not localStorage) |
| Refresh Token | {{REFRESH_TTL}} (e.g., 30d) | HttpOnly cookie |
| API Key | Non-expiring (rotatable) | Secure vault |

**Refresh Flow:**
```http
POST /auth/refresh
Cookie: refresh_token={{REFRESH_TOKEN}}
→ 200: { "access_token": "...", "expires_in": 3600 }
→ 401: Refresh token expired — re-authenticate
```

### 3.2 API Keys (for server-to-server)

```http
X-API-Key: ak_live_{{KEY_PREFIX_SHOWN_TO_USER}}
```

- API keys scoped to specific permissions
- Prefixed: `ak_live_` (production), `ak_test_` (test)
- Rotate via: `POST /api-keys/{id}/rotate`

### 3.3 OAuth2 Scopes

| Scope | Description | Grantable to |
|-------|------------|-------------|
| `{{resource}}:read` | Read {{resource}} data | All auth users |
| `{{resource}}:write` | Create/update {{resource}} | {{ROLE_REQUIRED}} |
| `{{resource}}:delete` | Delete {{resource}} | {{ROLE_REQUIRED}} |
| `admin:*` | Full admin access | Admin users only |

---

## 4. Common Headers

<!-- GUIDANCE: Define standard headers that apply to all requests and responses. -->

### Request Headers

| Header | Required | Description |
|--------|----------|-------------|
| `Authorization` | Yes (except public endpoints) | `Bearer {JWT}` or N/A |
| `Content-Type` | Yes (POST/PUT/PATCH) | `application/json` |
| `Accept` | No | `application/json` (default) |
| `X-Request-ID` | Recommended | UUID v4 — echoed in response for tracing |
| `X-Idempotency-Key` | Yes (POST mutations) | UUID v4 — prevents duplicate operations |
| `Accept-Language` | No | `en`, `no`, `de` — for localized error messages |

### Response Headers

| Header | Description |
|--------|-------------|
| `X-Request-ID` | Echo of request ID (or generated if not provided) |
| `X-RateLimit-Limit` | Rate limit ceiling |
| `X-RateLimit-Remaining` | Remaining requests in current window |
| `X-RateLimit-Reset` | Unix timestamp when rate limit resets |
| `X-Response-Time` | Server processing time in ms |
| `Cache-Control` | Caching directives |
| `ETag` | Entity tag for conditional requests |

---

## 5. Error Response Format (RFC 7807)

<!-- GUIDANCE: All errors MUST use the RFC 7807 Problem Details format. Consistent error format makes client integration dramatically easier. -->

```json
{
  "type": "https://api.{{DOMAIN}}/errors/{{ERROR_CODE}}",
  "title": "Human-readable error title",
  "status": 400,
  "detail": "Specific, actionable error description",
  "instance": "/api/v1/{{resource}}/{{id}}",
  "traceId": "{{TRACE_ID}}",
  "errors": [
    {
      "field": "{{FIELD_NAME}}",
      "code": "{{VALIDATION_CODE}}",
      "message": "{{FIELD_SPECIFIC_MESSAGE}}"
    }
  ]
}
```

### Standard Error Codes

| HTTP Status | Error Type | When to Use |
|------------|-----------|------------|
| `400` | `validation-error` | Request body/params fail validation |
| `401` | `unauthorized` | Missing or invalid authentication |
| `403` | `forbidden` | Authenticated but lacks permission |
| `404` | `not-found` | Resource does not exist |
| `405` | `method-not-allowed` | HTTP method not supported |
| `409` | `conflict` | Duplicate or state conflict |
| `410` | `gone` | Resource permanently deleted |
| `422` | `business-rule-violation` | Business logic rejection |
| `429` | `rate-limit-exceeded` | Too many requests |
| `500` | `internal-error` | Unexpected server error |
| `502` | `bad-gateway` | Upstream service failure |
| `503` | `service-unavailable` | Planned downtime or overload |

---

## 6. Pagination Strategy

<!-- GUIDANCE: Consistent pagination across all list endpoints. Cursor-based pagination preferred for large datasets. -->

**Strategy:** Cursor-based (preferred) | Offset-based (legacy support)

### Cursor-Based (default for all new endpoints)

**Request:**
```
GET /api/v1/{{resource}}?limit=20&after={{CURSOR}}
```

**Response:**
```json
{
  "data": [],
  "pagination": {
    "hasNextPage": true,
    "hasPreviousPage": false,
    "startCursor": "{{BASE64_CURSOR}}",
    "endCursor": "{{BASE64_CURSOR}}",
    "limit": 20
  }
}
```

### Offset-Based (legacy)

**Request:**
```
GET /api/v1/{{resource}}?page=1&limit=20
```

**Response:**
```json
{
  "data": [],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 500,
    "totalPages": 25
  }
}
```

**Limits:** Minimum 1, Maximum 100 items per request.

---

## 7. Rate Limiting

<!-- GUIDANCE: Define rate limits per tier. Include the headers used and the action on exceeding limits. -->

| Tier | Limit | Window | Scope |
|------|-------|--------|-------|
| Anonymous | {{N}} req | per minute | Per IP |
| Authenticated (free) | {{N}} req | per minute | Per API key |
| Authenticated (paid) | {{N}} req | per minute | Per API key |
| Admin | {{N}} req | per minute | Per user |

**Rate limit exceeded response:**
```http
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: {{LIMIT}}
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700003600
Retry-After: 37

{
  "type": "https://api.{{DOMAIN}}/errors/rate-limit-exceeded",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "You have exceeded {{N}} requests per minute. Retry after 37 seconds."
}
```

---

## 8. Endpoint Documentation

<!-- GUIDANCE: Document every endpoint. Use this format consistently. Add one section per endpoint group (resource). -->

### Resource: {{Resource Name}}

#### `POST /{{resource}}`

**Summary:** Create a new {{entity}}
**Auth:** Required | Scope: `{{resource}}:write`
**Idempotency:** Required — provide `X-Idempotency-Key`

**Request:**
```http
POST /api/v1/{{resource}} HTTP/1.1
Authorization: Bearer {{TOKEN}}
Content-Type: application/json
X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

{
  "{{field1}}": "string (required)",
  "{{field2}}": 123,
  "{{field3}}": "ENUM_VALUE_A | ENUM_VALUE_B"
}
```

**Response `201 Created`:**
```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "{{field1}}": "value",
  "{{field2}}": 123,
  "{{field3}}": "ENUM_VALUE_A",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "updatedAt": "2024-01-01T00:00:00.000Z"
}
```

**Error scenarios:**
| Scenario | Status | Error Code |
|---------|--------|-----------|
| Missing required `{{field1}}` | 400 | `validation-error` |
| `{{field1}}` exceeds max length | 400 | `validation-error` |
| Duplicate `{{unique_field}}` | 409 | `conflict` |
| Invalid enum value | 400 | `validation-error` |
| Business rule: {{RULE_DESCRIPTION}} | 422 | `business-rule-violation` |

---

#### `GET /{{resource}}/:id`

**Summary:** Retrieve a {{entity}} by ID
**Auth:** Required | Scope: `{{resource}}:read`
**Cache:** `ETag` + `Last-Modified` supported

**Path Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | `UUID` | The {{entity}} unique identifier |

**Response `200 OK`:**
```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "{{field1}}": "value",
  "createdAt": "2024-01-01T00:00:00.000Z"
}
```

**Error scenarios:**
| Scenario | Status | Error Code |
|---------|--------|-----------|
| Invalid UUID format | 400 | `validation-error` |
| {{entity}} not found | 404 | `not-found` |
| Access to other tenant's data | 403 | `forbidden` |

---

#### `GET /{{resource}}`

**Summary:** List {{entities}} with filtering and pagination
**Auth:** Required | Scope: `{{resource}}:read`

**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `limit` | `integer [1-100]` | `20` | Items per page |
| `after` | `string` | — | Cursor for next page |
| `before` | `string` | — | Cursor for previous page |
| `sort` | `string` | `createdAt:desc` | Sort: `{field}:{asc\|desc}` |
| `{{FILTER_1}}` | `string` | — | Filter by {{FILTER_1}} |
| `{{FILTER_2}}` | `string (ISO8601)` | — | Filter by date range: `after:DATE` |
| `search` | `string` | — | Full-text search |

**Response `200 OK`:**
```json
{
  "data": [
    { "id": "...", "{{field1}}": "..." }
  ],
  "pagination": {
    "hasNextPage": true,
    "endCursor": "{{CURSOR}}"
  }
}
```

---

## 9. Webhook Documentation

<!-- GUIDANCE: If the API sends webhooks, document the format, events, and verification mechanism. -->

### 9.1 Webhook Configuration

**Register endpoint:** `POST /webhooks`
**Test endpoint:** `POST /webhooks/{id}/test`
**Signature verification:** HMAC-SHA256

### 9.2 Signature Verification

```javascript
// Verify webhook authenticity
const payload = request.rawBody;
const signature = request.headers['X-Webhook-Signature'];
const secret = process.env.WEBHOOK_SECRET;

const expected = 'sha256=' + crypto
  .createHmac('sha256', secret)
  .update(payload)
  .digest('hex');

const isValid = crypto.timingSafeEqual(
  Buffer.from(signature),
  Buffer.from(expected)
);
```

### 9.3 Webhook Events

| Event | Description | Payload |
|-------|------------|---------|
| `{{entity}}.created` | {{entity}} created | `{id, ...}` |
| `{{entity}}.updated` | {{entity}} updated | `{id, changes: {...}}` |
| `{{entity}}.deleted` | {{entity}} deleted | `{id, deletedAt}` |

### 9.4 Webhook Delivery

- **Timeout:** 30 seconds per delivery attempt
- **Retries:** 5 attempts with exponential backoff (1min, 5min, 30min, 2h, 12h)
- **Success:** Any 2xx response
- **Dead delivery:** Alert and suspend after 5 consecutive failures

---

## 10. OpenAPI 3.1 YAML Skeleton

<!-- GUIDANCE: Embed the machine-readable OpenAPI spec. Keep in sync with the human-readable documentation above. Tools like Swagger UI, Redoc, and Stoplight can render this. -->

```yaml
openapi: '3.1.0'

info:
  title: '{{API_NAME}}'
  description: '{{API_DESCRIPTION}}'
  version: '{{API_VERSION}}'
  contact:
    name: '{{TEAM_NAME}}'
    email: '{{TEAM_EMAIL}}'
  license:
    name: 'Proprietary'

servers:
  - url: 'https://api.{{DOMAIN}}/v{{MAJOR_VERSION}}'
    description: 'Production'
  - url: 'https://api.staging.{{DOMAIN}}/v{{MAJOR_VERSION}}'
    description: 'Staging'

security:
  - bearerAuth: []

tags:
  - name: '{{Resource}}'
    description: 'Operations on {{resource}}'

paths:
  /{{resource}}:
    post:
      tags: ['{{Resource}}']
      summary: 'Create {{entity}}'
      operationId: 'create{{Entity}}'
      security:
        - bearerAuth: ['{{resource}}:write']
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Create{{Entity}}Request'
      responses:
        '201':
          description: 'Created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/{{Entity}}'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'

    get:
      tags: ['{{Resource}}']
      summary: 'List {{entities}}'
      operationId: 'list{{Entities}}'
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/after'
      responses:
        '200':
          description: 'OK'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Paginated{{Entity}}Response'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  parameters:
    limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20

    after:
      name: after
      in: query
      schema:
        type: string

  schemas:
    {{Entity}}:
      type: object
      required: [id, {{field1}}, createdAt]
      properties:
        id:
          type: string
          format: uuid
        {{field1}}:
          type: string
        createdAt:
          type: string
          format: date-time

    Create{{Entity}}Request:
      type: object
      required: [{{field1}}]
      properties:
        {{field1}}:
          type: string
          minLength: 1
          maxLength: 255

    ProblemDetails:
      type: object
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        instance:
          type: string
        traceId:
          type: string

    Paginated{{Entity}}Response:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/{{Entity}}'
        pagination:
          type: object
          properties:
            hasNextPage:
              type: boolean
            endCursor:
              type: string

  responses:
    ValidationError:
      description: 'Validation Error'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
    Unauthorized:
      description: 'Unauthorized'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
    Conflict:
      description: 'Conflict'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
```

---

## 11. SDK Generation Notes

<!-- GUIDANCE: If SDKs are generated from the OpenAPI spec, document the process and any customizations. -->

**Generation tool:** {{SDK_TOOL}} (e.g., openapi-generator, Speakeasy, Fern)
**Generated SDKs:**

| Language | Package | Registry |
|---------|---------|---------|
| TypeScript/JS | `@{{ORG}}/{{sdk-name}}` | npm |
| Python | `{{org}}-{{sdk-name}}` | PyPI |
| Go | `github.com/{{org}}/{{sdk-name}}` | pkg.go.dev |

**Generation command:**
```bash
openapi-generator-cli generate \
  -i ./api-specification.yaml \
  -g typescript-fetch \
  -o ./sdk/typescript \
  --additional-properties=npmName=@{{org}}/{{sdk-name}}
```

---

## 12. API Changelog

<!-- GUIDANCE: Log every API change here. Consumers rely on this to understand what changed and when. -->

| Version | Date | Change Type | Description |
|---------|------|------------|-------------|
| `{{API_VERSION}}` | {{DATE}} | Added | `POST /{{resource}}` endpoint |
| `{{API_VERSION}}` | {{DATE}} | Changed | `{{FIELD}}` is now optional (was required) |
| `{{API_VERSION}}` | {{DATE}} | Deprecated | `GET /{{old-resource}}` — use `GET /{{new-resource}}` |
| `{{API_VERSION}}` | {{DATE}} | Removed | `DELETE /{{old-resource}}` — deprecated since {{DATE}} |

---

## Approval
| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | | | |
| API Consumer Rep | | | |
| Security Review | | | |
| Tech Lead | | | |