
Grape handles exceptions with rescue_from blocks declared on your API class: rescue_from :all catches every exception, while rescue_from ActiveRecord::RecordNotFound targets one class. Inside an endpoint, error!("Not Found", 404) halts with a formatted error, defaulting to status 500. Without a handler, invalid params return 400 and other exceptions bubble up to Rack. This guide covers Grape 3.3.
Every response body, status code, and error message was verified on Grape 3.3.5, Rack 3.2.7, and Ruby 3.4.10. The examples run standalone — gem install grape rackup puma is the only setup.
How Grape Handles Errors by Default
Out of the box, Grape formats two kinds of failure on its own: errors you report through its error! helper, and the Grape::Exceptions classes it raises itself, such as the validation errors triggered by a missing required param. Any other exception — a database error, a RuntimeError from a typo — leaves Grape unformatted unless you declare a rescue_from handler for it.
A small API is enough to see all of this behavior:
# app.rb
require "grape"
JOBS = {
"1" => { id: 1, title: "Backend Engineer" }
}
class API < Grape::API
format :json
resource :jobs do
get ":id" do
job = JOBS[params[:id]]
job || error!({ error: "Not Found" }, 404)
end
end
params do
requires :name, type: String
requires :age, type: Integer
end
post :people do
{ name: params[:name], age: params[:age] }
end
end# config.ru
require_relative "app"
run APIStart the server with rackup, and the API answers on port 9292 (rackup’s default).
Raising Errors with error!
error! stops endpoint execution immediately and renders its first argument through the API’s error formatter, with the status you pass as the second argument:
curl http://localhost:9292/jobs/1
# => {"id":1,"title":"Backend Engineer"}
curl http://localhost:9292/jobs/2
# => {"error":"Not Found"}The first request responds with status 200; the second, where error! runs, with status 404.
The status argument is optional. Without it, error! responds with status 500:
get :broken do
error!("Something went wrong")
endGET /broken responds with status 500 and the body {"error":"Something went wrong"} — under format :json, a plain string message is wrapped in an error key.
error! covers failures your own code detects. For failures Ruby raises, the language gives you begin/rescue, covered in depth in Rescuing Exceptions in Ruby:
begin
# code that may raise an exception
rescue => e
# runs when an exception is raised
else
# runs when no exception is raised
ensure
# always runs
endrescue => e captures the exception into e; leaving the variable off — a bare rescue => — is a SyntaxError. Wrapping every endpoint in a begin block gets repetitive fast, though. Grape’s answer is rescue_from.
Rescuing Exceptions with rescue_from
rescue_from declares an exception handler for the whole API class. Pass an exception class to target one family of failures, or :all to catch everything:
class API < Grape::API
format :json
rescue_from :all do |e|
error!({ error: e.message }, 500)
end
get :boom do
raise "something went wrong"
end
endGET /boom now responds with status 500 and the body {"error":"something went wrong"} instead of a raw exception.
The handler block receives the exception and must end in one of three ways: call error!, return a Rack::Response, or raise. Anything else produces the 500 Invalid response error described in the next section. Raising from a handler does not restore Grape’s default behavior either: on Grape 3.3.5, the re-raised exception comes back as a generic 500 with the body {"error":"Internal Server Error"}. That makes error! the ending nearly every handler wants.
A single catch-all flattens every failure into one status code, which misleads API clients: a missing record is not a server error. Declare handlers for specific classes alongside the catch-all, and each failure keeps a meaningful status:
class JobNotFoundError < StandardError; end
class API < Grape::API
format :json
rescue_from JobNotFoundError do |e|
error!({ error: e.message }, 404)
end
rescue_from :all do |e|
error!({ error: e.message }, 500)
end
resource :jobs do
get ":id" do
raise JobNotFoundError, "job #{params[:id]} not found" unless JOBS[params[:id]]
JOBS[params[:id]]
end
end
endWith both handlers in place, GET /jobs/2 responds with status 404 and {"error":"job 2 not found"}, while any other exception still lands in the catch-all with a 500. In a Rails-backed API, rescue_from ActiveRecord::RecordNotFound fills the same slot.
A third argument to error! adds response headers:
rescue_from :all do |e|
error!({ error: e.message }, 500, { "Cache-Control" => "no-store" })
endOne gotcha deserves its own warning: rescue_from :all also catches Grape::Exceptions::ValidationErrors. With the catch-all handler in place, a request that fails validation no longer gets Grape’s default 400 — POST an empty body to the /people endpoint from the first example, and the response is status 500 with the body {"error":"name is missing, age is missing"}. A client mistake now reads as a server error, and re-raising is no way out. Declare a handler for validation errors instead:
rescue_from Grape::Exceptions::ValidationErrors do |e|
error!({ errors: e.full_messages }, 400)
endValidation failures now respond with status 400 and the body {"errors":["name is missing","age is missing"]} — full_messages returns each failure as its own element, a friendlier shape for clients than the joined default message.
Grape Error Messages and What They Mean
The errors in this section are the ones Grape produces itself, reproduced on Grape 3.3.5. Each one points at a specific mistake.
name is missing
A required parameter was not sent. Grape raises Grape::Exceptions::ValidationErrors and responds with status 400 before your endpoint code runs; under format :json, the body is {"error":"name is missing"}. Several failures join into one message — {"error":"name is missing, age is missing"} — and a parameter that arrives but fails type coercion produces the same shape with a different verb: sending age=abc to a requires :age, type: Integer param responds with {"error":"age is invalid"}. Without format :json, the same failure is a text/plain response whose entire body is name is missing.
The fix depends on which side of the API you are on: as a client, send the parameter the params block requires; as the API author, rescue Grape::Exceptions::ValidationErrors — as in the full_messages handler in the previous section — to reshape the response without losing the 400.
Invalid response
A rescue_from handler ended with a plain value — a hash, a string, nil — instead of calling error!, returning a Rack::Response, or raising:
rescue_from :all do |e|
{ error: e.message } # responds with status 500, {"error":"Invalid response"}
endGrape discards the returned value and responds with status 500 and the body {"error":"Invalid response"}, hiding the original exception entirely. The fix is to end the handler with error!({ error: e.message }, 500).
Exceptions That Bypass Grape
The last failure mode has no error string, and that absence is the symptom: without a rescue_from handler, an exception Grape does not own gets no formatted response at all. Raise a RuntimeError in an endpoint of a handler-less API and the exception propagates out of Grape to the Rack server. In development, rackup responds with Rack::ShowExceptions’ HTML debug page and prints the backtrace to the terminal; in production mode, the same request gets the server’s bare 500 with an empty body. Neither response is JSON, so your API’s contract breaks at the same moment as its code. The fix is a rescue_from :all backstop that turns unexpected exceptions into well-formed 500s — paired with error monitoring, so the exceptions it hides from clients stay visible to you.
Custom Error Classes in Grape
Handlers stay manageable when your application’s failures share a hierarchy. The pattern from Diving into Custom Exceptions in Ruby — a base class that owns shared behavior, subclasses that fill in specifics — maps directly onto Grape: give the base class a status, and one handler serves the whole family.
class BaseError < StandardError
attr_reader :status
def initialize(message = "Something unexpected happened", status = 500)
@status = status
super(message)
end
end
class NotFoundError < BaseError
def initialize(message = "The record you are looking for does not exist")
super(message, 404)
end
end
class API < Grape::API
format :json
rescue_from BaseError do |e|
error!({ error: e.message }, e.status)
end
rescue_from :all do |e|
error!({ error: e.message }, 500)
end
resource :jobs do
get ":id" do
raise NotFoundError unless JOBS[params[:id]]
JOBS[params[:id]]
end
end
endrescue_from BaseError matches subclasses too, so GET /jobs/2 responds with status 404 and {"error":"The record you are looking for does not exist"}: the status travels with the exception class, and adding a new error type means adding a subclass, not a handler.
A handler can return a Rack::Response instead of calling error!:
rescue_from :all do |e|
Rack::Response.new({ error: e.message }.to_json, 500)
endThis still works on Grape 3.3.5, with a catch: the response skips Grape’s formatter and ships without a Content-Type header. Prefer error! inside handlers — it keeps the formatter and rules out both the missing header and the Invalid response failure mode.
Grape has two more settings worth knowing. default_error_status changes the status a status-less error! responds with:
class API < Grape::API
format :json
default_error_status 422
get :broken do
error!("Something went wrong")
end
endGET /broken now responds with status 422 instead of 500. And rescue_from accepts a helper method in place of a block, which keeps a long list of handlers readable:
class API < Grape::API
format :json
helpers do
def handle_argument_error(e)
error!({ error: e.message }, 400)
end
end
rescue_from ArgumentError, with: :handle_argument_error
get :convert do
{ value: Integer(params[:value].to_s) }
end
endGET /convert?value=abc responds with status 400 and the body {"error":"invalid value for Integer(): \"abc\""}.
Best Practices for Grape Exception Handling
- Rescue specific exception classes alongside
rescue_from :all, so each failure keeps a meaningful status code and the catch-all stays a backstop. - Declare a handler for
Grape::Exceptions::ValidationErrorswhenever you userescue_from :all, so validation failures stay 400s instead of turning into 500s. - End every handler with
error!— it keeps the error formatter, the Content-Type header, and the original message, and it rules out theInvalid response500. - Group related failures under a base class that carries its own status, and rescue the base class once.
Monitoring Grape Exceptions with AppSignal
A rescue_from handler that returns a tidy JSON body also keeps the exception out of sight — in production, that silence is the problem. AppSignal’s Grape integration reports the exceptions your API raises, grouped by endpoint, so a failing route surfaces in your dashboard before an API consumer reports it.

Wrapping Up
Grape’s exception toolkit is small and predictable: error! for failures your code detects, rescue_from for everything raised, custom error classes to keep the two organized, and automatic 400s for validation failures. The sharp edges are the quiet ones — a catch-all that swallows validation errors, a handler that returns the wrong type, an unrescued exception that bypasses Grape entirely — and each has a short fix you can verify with curl.
Happy coding!
P.S. If you’d like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!
Frequently asked questions
- How do I rescue all exceptions in a Grape API?
- Declare rescue_from :all with a block on your API class; the block receives the exception and must call error!, return a Rack response, or re-raise. Rescue specific classes first so they get correct status codes.
- What does name is missing mean in Grape?
- It is Grape’s validation error for a required parameter that was not sent. Grape raises Grape::Exceptions::ValidationErrors and responds with status 400 before your endpoint code runs.
- What status code does Grape error! return by default?
- Without an explicit status, error! responds with 500. You can pass a status as the second argument, or change the default for an API with default_error_status.
- Does rescue_from :all catch validation errors in Grape?
- Yes. A rescue_from :all handler also receives Grape::Exceptions::ValidationErrors, so a handler that always returns 500 turns 400 validation errors into server errors. Rescue validation errors separately first.
Published , Updated
Wondering what you can do next?
- Subscribe to our Ruby Magic newsletter and never miss an article again.
- Start monitoring your Ruby app with AppSignal.
- Share this article on social media

Kingsley Chijioke
Our guest author Kingsley is a Software Engineer who enjoys writing technical articles.
All articles by Kingsley ChijiokeBecome our next author!
AppSignal monitors your apps
AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!


