api-patterns
Skill211 starsChanged 5 months agoMITView on GitHub
Builds REST APIs using respond_to blocks with Jbuilder templates following the 37signals same-controllers-different-formats philosophy. Use when adding API endpoints, JSON responses, token authentication, pagination, or when user mentions API, JSON, REST, or Jbuilder. WHEN NOT: For HTML-only controllers (use crud-patterns), for webhook delivery (use event-tracking).
14 other projects use a version of this.
Adopted by
Most recently taken up by
- sid-lakhani/agents-configtheir own version, first seen Oct 20, 2026
- xfstudio/skillstheir own version, first seen Oct 13, 2026
- xenitV1/Antigravity-Workflowstheir own version, first seen Oct 8, 2026
- vudovn/ag-kittheir own version, first seen Oct 8, 2026
- vudovn/antigravity-kittheir own version, first seen Oct 8, 2026
- Ghosteken/agent-harnesstheir own version, first seen Oct 1, 2026
Used with
Projects with it also use intelligent-routing, nextjs-react-expert and 5 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
SKB3002Skillintelligent-routing
Automatic agent selection and intelligent task routing. Analyzes user requests and automatically selects the best specialist agent(s) without requiring explicit user mentions.
Also in 5 projects that use this
SKB3002Skillnextjs-react-expert
React and Next.js performance optimization from Vercel Engineering. Use when building React components, optimizing performance, eliminating waterfalls, reducing bundle size, reviewing code for performance issues, or implementing server/client-side optimizations.
Also in 3 projects that use this
xfstudioSkillrust-pro
Master Rust 1.75+ with modern async patterns, advanced type system
Also in 5 projects that use this
jornalistainclusivoSkilldesign-spec
How to author a DESIGN.md file — the machine-readable design-token + human-rationale format that must exist before any UI is built. YAML front-matter token schema (colors, typography, spacing, rounded, components), type system, token references, and canonical section order.
Also in 3 projects that use this
zzfancitizenSkillverify-changes
Verify code changes do not break the project by running tests, linter checks, and import validation. Use after making any code changes, editing Python files, modifying imports, updating dependencies, or refactoring.
Also in 3 projects that use this
VercelSkillweb-design-guidelines
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
Also in 5 projects that use this
Other versions14 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 14
- vudovn/ag-kit★ 8.2ksame name
- vudovn/antigravity-kit★ 8.2ksame name
- bybren-llc/safe-agentic-workflow★ 405same name
- xenitV1/Antigravity-Workflows★ 130same name
- VoDaiLocz/kilo-kit-mcp★ 28same name
- xfstudio/skills★ 9same name
- SKB3002/agenthub★ 4same name
- ai4brands-design/claude-skills★ 2same name
- Ghosteken/agent-harness★ 2same name
- Iteam-company/claude-code-boilerplates★ 2same name
4 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository17 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .claude/CLAUDE.mdCLAUDE.md
- .opencode/CLAUDE.mdCLAUDE.md
- auth-setupSkill
- caching-patternsSkill
- concern-patternsSkill
- crud-patternsSkill
- event-trackingSkill
- job-patternsSkill
- mailer-patternsSkill
- migration-patternsSkill
- model-patternsSkill
- multi-tenant-setupSkill
- state-recordsSkill
- stimulus-patternsSkill
- testing-patternsSkill
- turbo-patternsSkill
The file
We split this skill into the rules it gives an agent
9 rules. 0 are common, 6 are distinctive, and 3 are very specific to this project.
0
Common
Most of the 14 edited versions we read keep these.
6
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 19
Use respond_to blocks for format-specific responses
- Very specific: line 371
Use same controllers for HTML and JSON (respond_to blocks)
- Distinctive: line 372
Use Jbuilder for JSON views (not inline JSON in controllers)
- Distinctive: line 377
Use ETags for HTTP caching
- Distinctive: line 378
Test both HTML and JSON responses
- Distinctive: line 387
Use GraphQL (stick to REST)
- Very specific: line 388
Create separate API controllers when respond_to works
- Distinctive: line 389
Use Active Model Serializers (use Jbuilder)
- Distinctive: line 393
Use session-based auth for API (use tokens)
“Common” is measured: kept by at least 6 in 10 of the 14 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
API Patterns
Philosophy: Same Controllers, Different Formats
- One controller serves both HTML (web) and JSON (API)
- Use
respond_toblocks for format-specific responses - RESTful routes only (no GraphQL, no custom endpoints unless necessary)
- Jbuilder for JSON templates (like ERB for HTML)
- HTTP status codes for success/errors
- Token-based authentication for API (not OAuth unless required)
Project Knowledge
Stack: Jbuilder for JSON views, RESTful routes, token-based API auth, same controllers for HTML and JSON, HTTP caching with ETags for API.
Multi-tenancy: API uses same account scoping (/accounts/:account_id/...),
token scoped to account.
Commands:
# Generate API token model
rails generate model ApiToken user:references account:references \
token:string last_used_at:datetime
# Test API endpoints
curl -H "Authorization: Bearer TOKEN" \
-H "Accept: application/json" \
http://localhost:3000/boardsPattern 1: Respond To Blocks
class BoardsController < ApplicationController
before_action :set_board, only: [:show, :edit, :update, :destroy]
def index
@boards = Current.account.boards.includes(:creator).order(created_at: :desc)
respond_to do |format|
format.html # renders index.html.erb
format.json # renders index.json.jbuilder
end
end
def show
respond_to do |format|
format.html
format.json
end
end
def create
@board = Current.account.boards.build(board_params)
@board.creator = Current.user
respond_to do |format|
if @board.save
format.html { redirect_to @board, notice: "Board created" }
format.json { render :show, status: :created, location: @board }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @board.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @board.update(board_params)
format.html { redirect_to @board, notice: "Board updated" }
format.json { render :show, status: :ok }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @board.errors, status: :unprocessable_entity }
end
end
end
def destroy
@board.destroy
respond_to do |format|
format.html { redirect_to boards_path, notice: "Board deleted" }
format.json { head :no_content }
end
end
private
def set_board
@board = Current.account.boards.find(params[:id])
end
def board_params
params.require(:board).permit(:name, :description)
end
endPattern 2: Jbuilder Templates
See @references/jbuilder-templates.md for full details.
# app/views/boards/index.json.jbuilder
json.array! @boards do |board|
json.extract! board, :id, :name, :description, :created_at, :updated_at
json.creator do
json.extract! board.creator, :id, :name, :email
end
json.url board_url(board, format: :json)
end
# app/views/boards/show.json.jbuilder
json.extract! @board, :id, :name, :description, :created_at, :updated_at
json.creator do
json.extract! @board.creator, :id, :name, :email
end
json.cards @board.cards, partial: "cards/card", as: :card
json.url board_url(@board, format: :json)
# app/views/cards/_card.json.jbuilder
json.extract! card, :id, :title, :description, :created_at, :updated_at
json.creator do
json.extract! card.creator, :id, :name
end
json.url board_card_url(card.board, card, format: :json)Pattern 3: API Token Authentication
See @references/api-auth.md for full details.
# app/models/api_token.rb
class ApiToken < ApplicationRecord
belongs_to :user
belongs_to :account
has_secure_token :token, length: 32
validates :name, presence: true
validates :token, presence: true, uniqueness: true
scope :active, -> { where(active: true) }
def use!
touch(:last_used_at)
end
end
# app/controllers/concerns/api_authenticatable.rb
module ApiAuthenticatable
extend ActiveSupport::Concern
included do
before_action :authenticate_from_token, if: :api_request?
end
private
def api_request?
request.format.json?
end
def authenticate_from_token
token = request.headers["Authorization"]
&.match(/Bearer (.+)/)&.captures&.first
if token
@api_token = ApiToken.active.find_by(token: token)
if @api_token
@api_token.use!
Current.user = @api_token.user
Current.account = @api_token.account
else
render json: { error: "Unauthorized" }, status: :unauthorized
end
else
render json: { error: "Unauthorized" }, status: :unauthorized
end
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include ApiAuthenticatable
skip_before_action :verify_authenticity_token, if: :api_request?
before_action :authenticate_user!, unless: :api_request?
endPattern 4: Error Handling
module ApiErrorHandling
extend ActiveSupport::Concern
included do
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity
rescue_from ActionController::ParameterMissing, with: :render_bad_request
end
private
def render_not_found(exception)
respond_to do |format|
format.html { raise exception }
format.json { render json: { error: "Not found" }, status: :not_found }
end
end
def render_unprocessable_entity(exception)
respond_to do |format|
format.html { raise exception }
format.json do
render json: {
error: "Validation failed",
details: exception.record.errors.as_json
}, status: :unprocessable_entity
end
end
end
def render_bad_request(exception)
respond_to do |format|
format.html { raise exception }
format.json do
render json: { error: "Bad request", message: exception.message },
status: :bad_request
end
end
end
endPattern 5: HTTP Caching for API
def index
@boards = Current.account.boards.includes(:creator).order(created_at: :desc)
respond_to do |format|
format.html
format.json do
if stale?(@boards)
render :index
end
end
end
end
def show
@board = Current.account.boards.find(params[:id])
respond_to do |format|
format.html
format.json do
if stale?(@board)
render :show
end
end
end
endPattern 6: Pagination
See @references/api-versioning.md for versioning details.
def index
@boards = Current.account.boards.includes(:creator)
.order(created_at: :desc)
.page(params[:page])
.per(params[:per_page] || 25)
respond_to do |format|
format.html
format.json do
response.headers["X-Total-Count"] = @boards.total_count.to_s
response.headers["X-Page"] = @boards.current_page.to_s
response.headers["X-Per-Page"] = @boards.limit_value.to_s
render :index
end
end
end
# app/views/boards/index.json.jbuilder
json.boards @boards do |board|
json.extract! board, :id, :name, :description, :created_at
json.url board_url(board, format: :json)
end
json.pagination do
json.current_page @boards.current_page
json.per_page @boards.limit_value
json.total_pages @boards.total_pages
json.total_count @boards.total_count
json.next_page boards_url(page: @boards.next_page, format: :json) if @boards.next_page
json.prev_page boards_url(page: @boards.prev_page, format: :json) if @boards.prev_page
endCursor-based alternative:
def index
@boards = Current.account.boards.order(created_at: :desc)
@boards = @boards.where("created_at < ?", Time.zone.parse(params[:before])) if params[:before]
@boards = @boards.limit(params[:limit] || 25)
respond_to do |format|
format.html
format.json
end
endPattern 7: Batch Operations
class Cards::BatchController < ApplicationController
before_action :set_board
def update
results, errors = [], []
batch_params[:cards].each do |card_params|
card = @board.cards.find(card_params[:id])
if card.update(card_params.except(:id))
results << card
else
errors << { id: card.id, errors: card.errors }
end
end
respond_to do |format|
format.json do
if errors.empty?
render json: { success: true, cards: results }, status: :ok
else
render json: { success: false, errors: errors }, status: :unprocessable_entity
end
end
end
end
endBoundaries
Always
- Use same controllers for HTML and JSON (
respond_toblocks) - Use Jbuilder for JSON views (not inline JSON in controllers)
- Return proper HTTP status codes (201, 404, 422, etc.)
- Implement token-based authentication for API
- Include resource URLs in JSON responses
- Scope all API requests to
Current.account - Use ETags for HTTP caching
- Test both HTML and JSON responses
Ask First
- Whether to version API (most apps don't need it initially)
- Pagination strategy (page-based vs cursor-based)
- Whether to support batch operations
- Rate limiting requirements
Never
- Use GraphQL (stick to REST)
- Create separate API controllers when
respond_toworks - Use Active Model Serializers (use Jbuilder)
- Inline JSON in controllers (use views)
- Skip authentication for API endpoints
- Return HTML errors for JSON requests
- Use session-based auth for API (use tokens)
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

