Comprehensive guide to building REST APIs that reach the Glory of REST (Richardson Maturity Level 3) in Ruby on Rails. Contains 47 rules across 9 categories, ordered by the request/response lifecycle — from resource URI design through hypermedia link relations to API evolution.
When to Apply
Reference these guidelines when:
Designing new REST API endpoints and resource URIs
Adding hypermedia controls (_links, affordances) to API responses
Implementing content negotiation with HAL, JSON:API, or vendor media types
Building paginated, filterable, sortable collection endpoints
Reviewing APIs for proper HTTP method semantics and status codes
Evolving APIs without breaking existing clients
Rule Categories by Priority
Priority
Category
Impact
Prefix
1
Resource Modeling
CRITICAL
res-
2
HTTP Method Semantics
CRITICAL
http-
3
Hypermedia & Link Relations
CRITICAL
link-
4
Status Codes & Response Headers
HIGH
status-
5
Content Negotiation & Media Types
HIGH
media-
6
Collection Patterns
MEDIUM-HIGH
coll-
7
Error Semantics
MEDIUM
err-
8
Caching & Conditional Requests
MEDIUM
cache-
9
API Evolution
LOW-MEDIUM
evolve-
Quick Reference
1. Resource Modeling (CRITICAL)
res-noun-based-uris - URIs must be nouns, not verbs
res-plural-collection-uris - Always use plural nouns for collections
res-limit-nesting-depth - Limit nested resources to max 2 levels
res-model-business-entities - Model business entities, not database tables
res-use-consistent-identifiers - Use opaque identifiers, never auto-increment IDs
res-sub-resources-for-relationships - Express relationships as sub-resources
2. HTTP Method Semantics (CRITICAL)
http-get-must-be-safe - Keep GET requests free of side effects
http-post-for-creation - Return 201 Created with Location header from POST
http-put-for-full-replacement - Use PUT only for full resource replacement
http-patch-for-partial-updates - PATCH for partial updates with merge semantics
http-delete-is-idempotent - Ensure DELETE is idempotent
http-head-for-metadata - Use HEAD for metadata without body transfer
http-idempotency-key - Use idempotency keys for safe POST retries
3. Hypermedia & Link Relations (CRITICAL)
link-self-link-every-resource - Include a self link in every resource
link-related-resource-links - Link to related resources instead of foreign keys
link-action-affordances - Expose available actions as conditional links
link-standard-relation-types - Use IANA-registered link relation types
link-entry-point - Provide a root API entry point
link-pagination-links - Use hypermedia links for pagination
link-embedded-vs-linked - Choose between embedding and linking
4. Status Codes & Response Headers (HIGH)
status-201-with-location - Return 201 Created with Location header
status-204-for-no-content - Return 204 No Content for empty responses
status-409-for-conflicts - Return 409 Conflict for state conflicts
status-202-for-async - Return 202 Accepted for async operations
status-allow-header-on-405 - Return 405 with Allow header for wrong methods
status-rate-limit-headers - Include rate limit headers in API responses
5. Content Negotiation & Media Types (HIGH)
media-accept-header-negotiation - Respect the Accept header for content negotiation
media-content-type-in-responses - Set the correct Content-Type in every response
media-vendor-media-types - Use vendor media types for API versioning
media-406-for-unsupported-types - Return 406 for unsupported media types
6. Collection Patterns (MEDIUM-HIGH)
coll-cursor-pagination - Use cursor-based pagination instead of offset
coll-link-header-pagination - Include pagination links in body and Link header
coll-filtering-via-query-params - Support filtering via typed query parameters
coll-sorting-convention - Support sorting with a standardized sort parameter
coll-field-selection - Support sparse fieldsets via fields parameter
7. Error Semantics (MEDIUM)
err-problem-details - Use Problem Details (RFC 9457) for errors
err-error-links - Include recovery links in error responses
err-machine-readable-codes - Use machine-readable error codes
err-auth-error-codes - Distinguish 401 Unauthorized from 403 Forbidden
8. Caching & Conditional Requests (MEDIUM)
cache-etag-conditional-get - Use ETags with stale? for conditional GET
cache-last-modified - Set Last-Modified for time-based validation
cache-cache-control-headers - Set explicit Cache-Control headers
cache-vary-header - Include Vary header for content-dependent caching
9. API Evolution (LOW-MEDIUM)
evolve-additive-changes-only - Make only additive changes to responses
evolve-deprecation-headers - Use Deprecation and Sunset headers
evolve-hateoas-reduces-versioning - Leverage HATEOAS to eliminate URL versioning
How to Use
Read individual reference files for detailed explanations and code examples:
Section definitions - Category structure and impact levels
Rule template - Template for adding new rules
Reference Files
File
Description
references/_sections.md
Category definitions and ordering
assets/templates/_template.md
Template for new rules
metadata.json
Version and reference information
1---2name: restful-hateoas3description: Community RESTful HATEOAS Best Practices4---5# Community RESTful HATEOAS Best Practices67Comprehensive guide to building REST APIs that reach the Glory of REST (Richardson Maturity Level 3) in Ruby on Rails. Contains 47 rules across 9 categories, ordered by the request/response lifecycle — from resource URI design through hypermedia link relations to API evolution.89## When to Apply1011Reference these guidelines when:12- Designing new REST API endpoints and resource URIs13- Adding hypermedia controls (_links, affordances) to API responses14- Implementing content negotiation with HAL, JSON:API, or vendor media types15- Building paginated, filterable, sortable collection endpoints16- Reviewing APIs for proper HTTP method semantics and status codes17- Evolving APIs without breaking existing clients1819## Rule Categories by Priority2021| Priority | Category | Impact | Prefix |22|----------|----------|--------|--------|23| 1 | Resource Modeling | CRITICAL | `res-` |24| 2 | HTTP Method Semantics | CRITICAL | `http-` |25| 3 | Hypermedia & Link Relations | CRITICAL | `link-` |26| 4 | Status Codes & Response Headers | HIGH | `status-` |27| 5 | Content Negotiation & Media Types | HIGH | `media-` |28| 6 | Collection Patterns | MEDIUM-HIGH | `coll-` |29| 7 | Error Semantics | MEDIUM | `err-` |30| 8 | Caching & Conditional Requests | MEDIUM | `cache-` |31| 9 | API Evolution | LOW-MEDIUM | `evolve-` |3233## Quick Reference3435### 1. Resource Modeling (CRITICAL)3637- [`res-noun-based-uris`](references/res-noun-based-uris.md) - URIs must be nouns, not verbs38- [`res-plural-collection-uris`](references/res-plural-collection-uris.md) - Always use plural nouns for collections39- [`res-limit-nesting-depth`](references/res-limit-nesting-depth.md) - Limit nested resources to max 2 levels40- [`res-model-business-entities`](references/res-model-business-entities.md) - Model business entities, not database tables41- [`res-use-consistent-identifiers`](references/res-use-consistent-identifiers.md) - Use opaque identifiers, never auto-increment IDs42- [`res-sub-resources-for-relationships`](references/res-sub-resources-for-relationships.md) - Express relationships as sub-resources4344### 2. HTTP Method Semantics (CRITICAL)4546- [`http-get-must-be-safe`](references/http-get-must-be-safe.md) - Keep GET requests free of side effects47- [`http-post-for-creation`](references/http-post-for-creation.md) - Return 201 Created with Location header from POST48- [`http-put-for-full-replacement`](references/http-put-for-full-replacement.md) - Use PUT only for full resource replacement49- [`http-patch-for-partial-updates`](references/http-patch-for-partial-updates.md) - PATCH for partial updates with merge semantics50- [`http-delete-is-idempotent`](references/http-delete-is-idempotent.md) - Ensure DELETE is idempotent51- [`http-head-for-metadata`](references/http-head-for-metadata.md) - Use HEAD for metadata without body transfer52- [`http-idempotency-key`](references/http-idempotency-key.md) - Use idempotency keys for safe POST retries5354### 3. Hypermedia & Link Relations (CRITICAL)5556- [`link-self-link-every-resource`](references/link-self-link-every-resource.md) - Include a self link in every resource57- [`link-related-resource-links`](references/link-related-resource-links.md) - Link to related resources instead of foreign keys58- [`link-action-affordances`](references/link-action-affordances.md) - Expose available actions as conditional links59- [`link-standard-relation-types`](references/link-standard-relation-types.md) - Use IANA-registered link relation types60- [`link-entry-point`](references/link-entry-point.md) - Provide a root API entry point61- [`link-pagination-links`](references/link-pagination-links.md) - Use hypermedia links for pagination62- [`link-embedded-vs-linked`](references/link-embedded-vs-linked.md) - Choose between embedding and linking6364### 4. Status Codes & Response Headers (HIGH)6566- [`status-201-with-location`](references/status-201-with-location.md) - Return 201 Created with Location header67- [`status-204-for-no-content`](references/status-204-for-no-content.md) - Return 204 No Content for empty responses68- [`status-409-for-conflicts`](references/status-409-for-conflicts.md) - Return 409 Conflict for state conflicts69- [`status-202-for-async`](references/status-202-for-async.md) - Return 202 Accepted for async operations70- [`status-allow-header-on-405`](references/status-allow-header-on-405.md) - Return 405 with Allow header for wrong methods71- [`status-rate-limit-headers`](references/status-rate-limit-headers.md) - Include rate limit headers in API responses7273### 5. Content Negotiation & Media Types (HIGH)7475- [`media-accept-header-negotiation`](references/media-accept-header-negotiation.md) - Respect the Accept header for content negotiation76- [`media-content-type-in-responses`](references/media-content-type-in-responses.md) - Set the correct Content-Type in every response77- [`media-vendor-media-types`](references/media-vendor-media-types.md) - Use vendor media types for API versioning78- [`media-406-for-unsupported-types`](references/media-406-for-unsupported-types.md) - Return 406 for unsupported media types7980### 6. Collection Patterns (MEDIUM-HIGH)8182- [`coll-cursor-pagination`](references/coll-cursor-pagination.md) - Use cursor-based pagination instead of offset83- [`coll-link-header-pagination`](references/coll-link-header-pagination.md) - Include pagination links in body and Link header84- [`coll-filtering-via-query-params`](references/coll-filtering-via-query-params.md) - Support filtering via typed query parameters85- [`coll-sorting-convention`](references/coll-sorting-convention.md) - Support sorting with a standardized sort parameter86- [`coll-field-selection`](references/coll-field-selection.md) - Support sparse fieldsets via fields parameter8788### 7. Error Semantics (MEDIUM)8990- [`err-problem-details`](references/err-problem-details.md) - Use Problem Details (RFC 9457) for errors91- [`err-validation-errors`](references/err-validation-errors.md) - Return structured validation errors92- [`err-error-links`](references/err-error-links.md) - Include recovery links in error responses93- [`err-machine-readable-codes`](references/err-machine-readable-codes.md) - Use machine-readable error codes94- [`err-auth-error-codes`](references/err-auth-error-codes.md) - Distinguish 401 Unauthorized from 403 Forbidden9596### 8. Caching & Conditional Requests (MEDIUM)9798- [`cache-etag-conditional-get`](references/cache-etag-conditional-get.md) - Use ETags with stale? for conditional GET99- [`cache-last-modified`](references/cache-last-modified.md) - Set Last-Modified for time-based validation100- [`cache-cache-control-headers`](references/cache-cache-control-headers.md) - Set explicit Cache-Control headers101- [`cache-vary-header`](references/cache-vary-header.md) - Include Vary header for content-dependent caching102103### 9. API Evolution (LOW-MEDIUM)104105- [`evolve-additive-changes-only`](references/evolve-additive-changes-only.md) - Make only additive changes to responses106- [`evolve-deprecation-headers`](references/evolve-deprecation-headers.md) - Use Deprecation and Sunset headers107- [`evolve-hateoas-reduces-versioning`](references/evolve-hateoas-reduces-versioning.md) - Leverage HATEOAS to eliminate URL versioning108109## How to Use110111Read individual reference files for detailed explanations and code examples:112113- [Section definitions](references/_sections.md) - Category structure and impact levels114- [Rule template](assets/templates/_template.md) - Template for adding new rules115116## Reference Files117118| File | Description |119|------|-------------|120| [references/_sections.md](references/_sections.md) | Category definitions and ordering |121| [assets/templates/_template.md](assets/templates/_template.md) | Template for new rules |122| [metadata.json](metadata.json) | Version and reference information |
Run npx skillmds@latest add comeonoliver/restful-hateoas in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Community RESTful HATEOAS Best Practices It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Independent scanners report: SkillSpector: PASS, Skill Scanner: PASS. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
ComeOnOliver (@comeonoliver) published this skill. Their other Agent Skills are listed on their SkillMD profile.