AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Api Patterns

skill-thibautbaissac-rails-ai-agents-api-patterns · by ThibautBaissac

>-

No reviews yet
0 installs
33 views
0.0% view→install

Install

$ agentstack add skill-thibautbaissac-rails-ai-agents-api-patterns

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-thibautbaissac-rails-ai-agents-api-patterns)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Api Patterns? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

API Patterns

Philosophy: Same Controllers, Different Formats

  • One controller serves both HTML (web) and JSON (API)
  • Use respond_to blocks 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/boards

Pattern 1: Respond To Blocks

class BoardsController  { 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?
end

Pattern 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
end

Pattern 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
end

Pattern 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
end

Cursor-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
end

Pattern 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
end

Boundaries

Always

  • Use same controllers for HTML and JSON (respond_to blocks)
  • 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_to works
  • 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)

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.