User Story Template for API Products
API products serve developers as users, and the interface is an endpoint rather than a UI. This fundamental difference requires a user story format that captures what consumer product stories never need: the request/response contract, error codes as first-class requirements, rate limiting behavior, authentication scheme, and whether the change breaks existing integrations.
This template adapts the standard "As a user, I want... so that..." format for API-first products and developer platforms. It ensures that every API story specifies the complete endpoint contract before engineering begins — not just the happy path, but every error state, edge case, and SDK compatibility requirement. Use this template for REST API endpoints, webhook definitions, SDK method additions, and GraphQL queries or mutations.
What makes API user stories different from product user stories
The most common failure in API user stories is treating the endpoint as a single success case. A user story that says "As a developer, I want to create a payment so that my users can check out" has specified zero percent of what engineering needs to build an API endpoint. The request schema, response schema, authentication scheme, error cases, rate limits, and idempotency behavior are all missing — and all of them are required for the endpoint to work correctly in production.
Error handling in particular deserves first-class treatment in API stories. Consumer product stories rarely need to specify what happens when the user submits a form with invalid data — the UI handles it with inline validation. API stories must specify the exact error response format, error code, HTTP status code, and error message for every failure case. A developer who calls your API and receives an undocumented 500 error cannot self-serve the resolution — they must contact support.
Breaking change assessment is unique to API stories. Every time you modify an existing endpoint, you must determine whether the change could break existing integrations. Adding an optional field is non-breaking. Removing a field is breaking. Changing the type of a field is breaking. Changing an error code is breaking for any client that switches on that error code. This assessment must happen at the story level — discovering mid-implementation that a change is breaking when you thought it was additive is an expensive surprise.
Template sections
5 sections covering the complete user story workflow.
Developer persona and integration context
Replace the traditional user persona with a developer persona that captures the integration context: the language or framework they are using, their experience level with your API, and the integration goal they are trying to achieve. Different developer personas have fundamentally different requirements — a backend engineer integrating via cURL is different from a mobile developer using your iOS SDK.
Developer persona: Backend engineer integrating a Python webhook processor - Language/framework: Python 3.10+ with Flask or FastAPI - Experience level with our API: Intermediate — has integrated the authentication endpoints, new to webhooks - Integration context: Building a payment reconciliation system that processes charge events from our webhook stream - Integration goal: Receive webhook events reliably, verify their authenticity, and process them idempotently User story: As a Python developer building a payment reconciliation system, I want to receive webhook events for payment status changes, verify their HMAC signature, and process them with guaranteed delivery so that my reconciliation system stays accurate even if processing is delayed or a server restarts.
Tips
- The developer persona determines what SDK examples you need to include in documentation — specify the language so the PM knows which examples to prioritize
- Experience level affects error message quality: experienced API users want short error codes; new users need descriptive error messages with links to documentation
- The integration goal reveals whether the developer needs streaming, webhooks, polling, or synchronous responses — this affects the API design, not just the implementation
Endpoint contract specification
The acceptance criteria for an API story is the endpoint contract: the complete specification of what a correct request looks like and what a correct response looks like. This must be specified before engineering begins — it is the contract that determines what "done" means.
Endpoint: POST /v2/webhooks/endpoints Authentication: Bearer token (API key) — must be a project-scoped key, not a user token Request schema: ```json { "url": "https://...", // required, HTTPS only, must respond 200 within 5s "events": ["payment.succeeded", "payment.failed"], // required, at least 1 "secret": "whsec_...", // optional — if omitted, one is auto-generated "description": "...", // optional, max 255 chars "enabled": true // optional, default: true } ``` Response (201 Created): ```json { "id": "we_abc123", "url": "https://...", "events": ["payment.succeeded", "payment.failed"], "secret": "whsec_...", // returned once — developer must save this "enabled": true, "created_at": "2025-03-15T10:00:00Z" } ```
Tips
- Use JSON Schema notation for the contract — it is precise enough to generate documentation and test fixtures automatically
- For every request field: note whether it is required or optional, the type, any validation constraints (min/max length, format), and the behavior when omitted
- For response fields: note any that are only returned under specific conditions, and any that contain sensitive data that should be redacted in logs
Error handling specification
Specify every error case as a first-class acceptance criterion. For each error: the HTTP status code, the error code in the response body, the error message format, and whether the request is retriable. This section is as important as the happy path — developers write error handling code against this specification.
Error handling specification: | Error case | HTTP status | Error code | Retriable? | Message | |---|---|---|---|---| | URL is not HTTPS | 422 | INVALID_URL | No | "Webhook URL must use HTTPS" | | URL does not respond 200 within 5s during validation | 422 | URL_VALIDATION_FAILED | No | "Webhook URL validation failed: {reason}" | | Invalid event type | 422 | INVALID_EVENT_TYPE | No | "Unknown event type: {value}. See /v2/webhooks/events for valid types." | | API key is project-scoped | 403 | PERMISSION_DENIED | No | "Webhook endpoints require a project-scoped API key" | | Rate limit exceeded | 429 | RATE_LIMIT_EXCEEDED | Yes, after Retry-After header | "Rate limit exceeded. Retry after {seconds} seconds." | | Internal server error | 500 | INTERNAL_ERROR | Yes, with exponential backoff | "An internal error occurred. If this persists, contact support with trace ID: {trace_id}" | Error response format (all errors): ```json {"error": {"code": "INVALID_URL", "message": "Webhook URL must use HTTPS", "param": "url"}} ```
Tips
- Include the "param" field in validation errors — it tells the developer which request field caused the error without them having to guess
- Mark all 5xx errors as retriable — they represent server-side failures that may resolve on retry
- Include the trace ID in 5xx error messages — it dramatically reduces support resolution time because the engineer can pull the exact logs for the failed request
Rate limiting and idempotency
Specify the rate limit for the endpoint and the idempotency behavior. Rate limits prevent abuse and protect service reliability — they must be specified in the story so engineering can implement them consistently. Idempotency keys are critical for any endpoint that creates resources or processes payments — they prevent duplicate operations when clients retry after a network failure.
Rate limiting: - Endpoint: POST /v2/webhooks/endpoints - Limit: 100 requests per minute per API key - Response when exceeded: 429 with Retry-After header (seconds until limit resets) - Burst: Allow up to 20 requests in a 1-second window before throttling Idempotency: - This endpoint supports idempotency keys via the Idempotency-Key header - If the same Idempotency-Key is used within 24 hours: return the original 201 response (do not create a duplicate) - If the same Idempotency-Key is used with a different request body: return 422 with error code IDEMPOTENCY_KEY_CONFLICT - Idempotency key TTL: 24 hours
Tips
- Any endpoint that creates a resource should support idempotency — this is especially critical for payment-related endpoints where duplicate processing has financial consequences
- Rate limits should be documented per-endpoint in the API reference, not just stated in terms of service — developers write retry logic against rate limit specifications
- The Retry-After header is more useful than a fixed "try again later" message — it lets developers implement precise retry logic without guessing
Breaking change assessment and versioning
For every API story: assess whether the change breaks existing integrations. Non-breaking changes can ship in the same API version. Breaking changes require a new version, a deprecation notice, and a migration guide. This assessment must happen at the story level — not after the PR is in review.
Breaking change assessment: Change: Adding `enabled` field to the webhook endpoint creation request Is this breaking? - Adding an OPTIONAL field to a request: NON-BREAKING (existing clients that do not send the field will get default behavior: enabled: true) - Adding a REQUIRED field to a request: BREAKING (existing clients that do not send the field will get a validation error) - Removing a field from a response: BREAKING (clients that read the field will get undefined/null instead of the expected value) This change: NON-BREAKING — `enabled` is optional with a documented default. Versioning required: NO — can ship in v2. Documentation update required: - Add `enabled` to the endpoint reference documentation - Add `enabled` to the request schema in the API playground - Add changelog entry: "Added optional `enabled` field to POST /v2/webhooks/endpoints"
Tips
- Create a "breaking vs. non-breaking" checklist that engineers can run against every PR — this catches breaking changes before they ship
- Renaming a field (even with the old name kept as an alias) should be treated as breaking until you verify that no production client uses the old name
- For breaking changes: implement the new version while keeping the old version running — set a specific end-of-life date (at least 6 months out) and communicate it in the release notes
Copy-paste template
## User Story (API Product)
**Story:**
As a [developer persona — language/framework and integration context], I want to [API action or capability], so that [integration outcome].
**Priority:** [P0 / P1 / P2]
**API version:** [v1 / v2 / new]
---
### Endpoint Contract
**Method and path:** `[HTTP METHOD] /api/vX/[resource]`
**Authentication:** [Bearer API key / OAuth / No auth — specify scope required]
**Request:**
```json
{
"[field_name]": "[type]", // required — [description and constraints]
"[field_name]": "[type]" // optional — [description, default value]
}
```
**Response (success):**
- Status code: [201 / 200 / 204]
```json
{
"[field_name]": "[type]", // [description]
"[field_name]": "[type]" // [description]
}
```
---
### Error Handling
| Error case | HTTP status | Error code | Retriable? |
|---|---|---|---|
| [Invalid input] | 422 | [CODE] | No |
| [Not found] | 404 | [CODE] | No |
| [Unauthorized] | 401 | [CODE] | No |
| [Rate limited] | 429 | [CODE] | Yes |
| [Server error] | 500 | INTERNAL_ERROR | Yes |
**Error response format:**
```json
{"error": {"code": "ERROR_CODE", "message": "Human-readable message", "param": "field_name_if_applicable"}}
```
---
### Rate Limiting
- Rate limit: [X requests per minute] per [API key / IP / org]
- Response when exceeded: 429 with `Retry-After: [N]` header
- Idempotency key support: [Yes — TTL: 24 hours / No]
---
### Breaking Change Assessment
- Is this a new endpoint? [Yes — non-breaking / No]
- Modifications to existing endpoint: [List changes]
- Each change is: [Breaking / Non-breaking — reasoning]
- API versioning required: [Yes — new endpoint version / No — additive change]
- Deprecation notice required: [Yes — for: / No]
---
### SDK Compatibility
| SDK | Update required | Change type |
|---|---|---|
| Node.js / TypeScript | [Yes / No] | [New method / Modified parameters] |
| Python | [Yes / No] | [New method / Modified parameters] |
| Go | [Yes / No] | [New method / Modified parameters] |
---
### Acceptance Criteria
- [ ] Endpoint returns 201 with correct response schema for valid request
- [ ] Each error case returns the correct HTTP status and error code
- [ ] Rate limiting returns 429 with Retry-After header when exceeded
- [ ] Idempotency key prevents duplicate creation (if applicable)
- [ ] Authentication requirement enforced — 401 for missing/invalid token
- [ ] Breaking change assessment is documented and approved
- [ ] API documentation updated before releaseFrequently asked questions
Generate instead of filling in templates
Connect your tools, and Vantage generates the content using real product data. Free to start.
Free to start. No credit card required.