⚡ Promptolis Original · Coding & Development
🌐 API Endpoint Designer
REST vs RPC decision, resource naming consistent with your codebase, error response schema — plus the 2 endpoints that seem needed but create coupling debt.
Why this is epic
Most API design tools produce 'best practice' endpoints that don't match your existing style. This Original designs endpoints CONSISTENT with your codebase — including your error schema, auth patterns, and naming conventions.
Flags the 2 endpoints in every feature spec that LOOK useful but actually create coupling debt — the hidden tech debt that ships as 'just one more endpoint' and becomes unmaintainable.
Resolves the REST-vs-RPC decision per endpoint, not per project. Some operations genuinely don't fit REST (e.g., `POST /users/{id}/reset-password`); naming them awkwardly makes the API worse, not better.
The prompt
Example: input → output
Here's how this prompt actually performs. Real input below, real output from Claude Opus 4.
<feature-spec>Users should be able to create 'reading lists' — curated collections of articles from our main content system. They can create multiple lists, add/remove articles, share lists publicly (with a shareable URL), and optionally let others suggest articles to their lists (which the owner approves or rejects). Articles exist in our main content system with their own resource URLs.</feature-spec>
<existing-api-examples>
1. `GET /api/v1/articles/{slug}` → returns article { id, slug, title, content_html, author, published_at }
2. `POST /api/v1/articles` with body { title, content_md } → 201 { id, slug, ... }
3. `PATCH /api/v1/articles/{slug}` with body { title?, content_md? } → 200 { id, slug, ... }
4. `GET /api/v1/users/{id}` → returns user { id, handle, display_name, created_at }
5. `DELETE /api/v1/articles/{slug}` → 204
</existing-api-examples>
<error-schema>All errors return JSON: { error: { code: string, message: string, details?: object } } with appropriate HTTP status. Codes use snake_case like 'not_found', 'unauthorized', 'validation_failed'.</error-schema>
<auth-pattern>Bearer tokens in Authorization header. JWT contains user_id and scope array.</auth-pattern>
<api-audience>Mixed — first-party web/mobile clients AND a public developer API. Public devs can read articles but not create/modify.</api-audience>
<constraints>Backward compat critical (existing clients shipping). Slug-based URLs are established convention. Must support pagination (existing endpoints use cursor-based).</constraints>
API Design: Reading Lists Feature
An API design is a contract between clients and server that must be consistent with existing patterns, honest about coupling costs, and designed to evolve over multiple years. For the reading list feature, the key decisions are (1) how to name resources consistently with your existing slug convention, (2) how to handle the suggestion workflow without creating RPC-style endpoints that break your REST pattern, and (3) which endpoints in the obvious list actually create coupling debt and should NOT exist.
Style Calibration
Your existing API uses kebab-case paths, slug-based resource identification, and snake_case in JSON bodies. Resources are hierarchical (/articles/{slug}, /users/{id}). You mix numeric IDs (users) with slugs (articles) — this works because articles have stable human-readable slugs and users don't. For reading lists, I'll use slugs for public sharing and numeric IDs for internal references, which matches your existing pattern. Your versioning is URL-path based (/api/v1/). Your error schema is consistent and well-designed.
The Endpoints
`GET /api/v1/users/{user_id}/reading-lists`
- Intent: List a user's reading lists. Own lists + public lists of others.
- Request:
Authorization: Bearer <token>, query params:?cursor=xxx&limit=20 - Response:
200 { lists: [{ id, slug, name, description, is_public, article_count, created_at }], next_cursor } - Error cases:
401 unauthorized(missing/invalid token),404 not_found(user doesn't exist) - Auth: Own lists — all auth users. Public lists of others — any auth user including public devs.
- Idempotency: Safe, cacheable.
`POST /api/v1/reading-lists`
- Intent: Create a new reading list for the authenticated user.
- Request:
Authorization: Bearer <token>, body:{ name, description?, is_public? }(is_public defaults to false) - Response:
201 { id, slug, name, description, is_public, article_count: 0, created_at, owner: { id, handle } } - Error cases:
401 unauthorized,422 validation_failed(name too short/long, duplicate name per user if we enforce) - Auth: Any authenticated user. Not available to read-only public devs.
`GET /api/v1/reading-lists/{slug}`
- Intent: Get a single reading list with its articles.
- Request:
Authorization: Bearer <token>, optional?include=articles - Response:
200 { id, slug, name, description, is_public, owner, article_count, articles: [...], created_at } - Error cases:
404 not_found(doesn't exist or private list not owned by caller) - Auth: Any auth user for public lists; only owner for private lists. Public devs: public only.
- Note: Use 404 instead of 403 for private lists to avoid revealing existence.
`PATCH /api/v1/reading-lists/{slug}`
- Intent: Update name, description, or visibility.
- Request:
Authorization: Bearer <token>, body:{ name?, description?, is_public? } - Response:
200 { id, slug, name, description, is_public, ... } - Error cases:
401 unauthorized,403 forbidden(not owner),404 not_found,422 validation_failed - Auth: Owner only.
- Idempotency: Yes.
`DELETE /api/v1/reading-lists/{slug}`
- Intent: Delete a reading list.
- Response:
204 No Content - Error cases:
401,403 forbidden,404 not_found - Auth: Owner only.
`PUT /api/v1/reading-lists/{slug}/articles/{article_slug}`
- Intent: Add an article to the list. Idempotent (re-adding is a no-op, not an error).
- Request:
Authorization: Bearer <token>, no body required, optional{ note }for user annotation - Response:
204 No Content(article was added or already present) - Error cases:
401,403 forbidden(not owner),404 not_found(list or article doesn't exist) - Auth: Owner only. For suggestions workflow, see below.
- Idempotency: Yes — this is why
PUTnotPOST.
`DELETE /api/v1/reading-lists/{slug}/articles/{article_slug}`
- Intent: Remove an article from the list.
- Response:
204 - Error cases: Standard.
- Auth: Owner only.
`POST /api/v1/reading-lists/{slug}/suggestions`
- Intent: Someone other than the owner suggests an article for this list. Owner must approve.
- Request: Body:
{ article_slug, note? } - Response:
201 { suggestion_id, status: 'pending', suggested_by: { id, handle }, article_slug, note, created_at } - Error cases:
401,403 forbidden(list doesn't accept suggestions, or caller is the list owner trying to suggest to themselves),404 not_found,409 conflict(article already suggested by this user) - Auth: Any auth user who isn't the owner.
`POST /api/v1/reading-lists/{slug}/suggestions/{suggestion_id}/approve`
`POST /api/v1/reading-lists/{slug}/suggestions/{suggestion_id}/reject`
- Intent: Owner approves or rejects a pending suggestion. These are RPC-style because they're state transitions, not resource manipulation.
- Request: No body (or
{ note }for reject). - Response:
200 { suggestion_id, status: 'approved' | 'rejected' }. On approve, also adds the article to the list. - Error cases:
401,403 forbidden(not owner),404 not_found,409 conflict(already processed) - Auth: Owner only.
- Idempotency: Yes — approving already-approved is a no-op 200.
The REST-vs-RPC Decisions
All resource CRUD operations use REST verbs (GET, POST, PUT, PATCH, DELETE).
The two RPC-style endpoints — /suggestions/{id}/approve and /suggestions/{id}/reject — are justified because they represent state transitions on a resource, which REST doesn't handle cleanly. Alternative would be PATCH /suggestions/{id} with body { status: 'approved' } — valid REST, but: (1) it lets a client set the status to arbitrary values that your server must validate, and (2) /approve + /reject make the allowed transitions explicit in the URL and matches how your business logic actually works. Your existing API has no precedent either way, so we're setting the pattern.
The 2 Endpoints That Seem Needed But Create Coupling Debt
❌ `GET /api/v1/articles/{article_slug}/reading-lists` (DO NOT BUILD)
Why it looks useful: 'Show me which reading lists contain this article.'
Why it's debt: This endpoint tightly couples articles to reading lists — every article page must now know about reading lists. If you ever remove or rename the reading-list feature, every article-detail page breaks. Also, this endpoint has unclear pagination semantics (what's the cursor? list ID?) and ambiguous auth (do I see private lists that contain this article if I'm in them?).
Build instead: GET /api/v1/reading-lists?contains_article={slug} — a filtered list query. This keeps the articles API unaware of reading lists and gives clean pagination from the existing lists endpoint.
❌ `POST /api/v1/reading-lists/bulk-actions` (DO NOT BUILD)
Why it looks useful: 'Let users reorder/delete multiple articles in one request.'
Why it's debt: Bulk actions endpoints are a classic coupling trap. They start simple ('just reorder 3 articles') and within 6 months grow to: bulk add, bulk remove, bulk move between lists, bulk privacy-change. Each new action bloats the endpoint, error responses become complex (partial success?), and it becomes the tech-debt endpoint your team maintains forever.
Build instead: Use the individual endpoints with client-side batching if needed. For actual performance-critical bulk operations, add a specific RPC endpoint (POST /reading-lists/{slug}/reorder with body { article_slugs: [...] }) — not a generic bulk-actions endpoint.
Error Response Schema for These Endpoints
| Error code | HTTP | When |
|---|---|---|
| `unauthorized` | 401 | Missing or invalid token |
| `forbidden` | 403 | Authenticated but not allowed (not owner, read-only API key trying to write) |
| `not_found` | 404 | Resource doesn't exist OR private list not visible to caller |
| `validation_failed` | 422 | Request body failed validation; `details: { field: reason }` |
| `conflict` | 409 | Suggestion already exists, suggestion already processed |
| `rate_limit_exceeded` | 429 | If/when rate limits apply |
All follow your existing schema: { error: { code, message, details? } }.
Versioning Considerations
The two most likely evolutions of this API:
1. Adding collaboration (multiple owners per list). Handle this by keeping `owner` as a single-value field in v1 responses, but building the data model with many-to-many. When you ship v2, `owner` can become `owners: [...]` with a dual-response period.
2. Adding tags/sections within a list. Don't build this into v1. When it ships, add `section_id?` to the article-in-list shape — backward compatible as long as existing clients ignore unknown fields (which they should).
Both changes can be handled within /api/v1/ with response-shape evolution rather than a v2 URL bump. The only change that would force a v2 is if you dropped the slug-based URLs, which is unlikely.
For the OpenAPI Spec
paths:
/users/{user_id}/reading-lists:
get: { summary: 'List a user\'s reading lists' }
/reading-lists:
post: { summary: 'Create reading list' }
/reading-lists/{slug}:
get: { summary: 'Get reading list' }
patch: { summary: 'Update reading list' }
delete: { summary: 'Delete reading list' }
/reading-lists/{slug}/articles/{article_slug}:
put: { summary: 'Add article to list (idempotent)' }
delete: { summary: 'Remove article from list' }
/reading-lists/{slug}/suggestions:
post: { summary: 'Suggest an article' }
/reading-lists/{slug}/suggestions/{suggestion_id}/approve:
post: { summary: 'Approve suggestion' }
/reading-lists/{slug}/suggestions/{suggestion_id}/reject:
post: { summary: 'Reject suggestion' }
Key Takeaways
- Consistency with YOUR codebase beats consistency with the internet. Match existing patterns.
- RPC-style endpoints are fine when they represent state transitions. Don't shoehorn every operation into REST.
- Use 404 instead of 403 for private resources to avoid revealing existence — especially important for user-owned resources.
- Resist 'just one more endpoint' syndrome. The 2 coupling-debt endpoints you avoided here will save you 40+ hours over the next 2 years.
- Idempotency matters on
PUToperations. Clients will retry; your endpoints must handle it cleanly.
Common use cases
- Backend engineers designing a new feature's API surface
- Tech leads reviewing a junior dev's API proposal
- Startup founders designing v1 APIs that need to evolve without breaking in year 2
- Teams migrating from monolith to microservices who need to decide service boundaries
- Platform engineers designing internal service-to-service APIs
- Consultants auditing an existing API and proposing improvements
- Engineers writing the OpenAPI spec BEFORE building the service
Best AI model for this
Claude Opus 4 or GPT-5 Thinking. API design requires holding multiple concerns in mind (consistency, coupling, evolution path, security) — mid-tier models miss the coupling-debt detection which is the highest-value output.
Pro tips
- Always paste 3-5 existing endpoints from your codebase. The Original matches YOUR style — not the internet's idea of 'best practice.'
- Include your current error response schema. Consistent error handling matters more than clever endpoint design.
- Specify your auth pattern (Bearer tokens, session cookies, API keys, OAuth). It affects how you design the endpoints.
- List the ACTUAL user intents, not the data models. 'User wants to share a playlist' drives better design than 'endpoint for playlist sharing.'
- For internal APIs, be explicit about your service-mesh/RPC infrastructure. gRPC and REST have different design patterns.
- Don't skip the 'coupling debt' section. Those endpoints are the ones your future-self will curse.
Customization tips
- Always paste 3-5 existing endpoints — the Original matches YOUR style. Without them, you get generic REST-101 design that clashes with your codebase.
- Be honest about your audience. Internal-only APIs can have different conventions than public APIs. The design changes significantly.
- For features that involve state transitions (approve, publish, submit, deliver), expect and welcome RPC-style endpoints. Forcing them into REST produces ugly APIs.
- Save the coupling-debt section outputs across projects. You'll notice patterns in WHICH endpoints your team tends to over-design.
- For v1 APIs specifically: decide versioning strategy BEFORE shipping. URL-based (/v1/) is simplest; header-based is more flexible but more complex. Picking later is painful.
Variants
GraphQL Mode
For GraphQL schemas instead of REST — designs the types, mutations, and queries, plus the N+1 concerns specific to GraphQL that REST avoids.
Versioning Strategy
For teams about to ship v2 of an API. Designs the endpoints PLUS the deprecation path, header-based vs URL-based versioning choice, and the migration timeline.
Event-Driven / Webhook
For features that need webhook or event-streaming rather than request-response. Designs the event schema, delivery guarantees, and retry/backoff policy.
Frequently asked questions
How do I use the API Endpoint Designer prompt?
Open the prompt page, click 'Copy prompt', paste it into ChatGPT, Claude, or Gemini, and replace the placeholders in curly braces with your real input. The prompt is also launchable directly in each model with one click.
Which AI model works best with API Endpoint Designer?
Claude Opus 4 or GPT-5 Thinking. API design requires holding multiple concerns in mind (consistency, coupling, evolution path, security) — mid-tier models miss the coupling-debt detection which is the highest-value output.
Can I customize the API Endpoint Designer prompt for my use case?
Yes — every Promptolis Original is designed to be customized. Key levers: Always paste 3-5 existing endpoints from your codebase. The Original matches YOUR style — not the internet's idea of 'best practice.'; Include your current error response schema. Consistent error handling matters more than clever endpoint design.
Explore more Originals
Hand-crafted 2026-grade prompts that actually change how you work.
← All Promptolis Originals