Actions
Actions define the request and response shape for each endpoint.
# app/contracts/api/v1/post_contract.rb
action :create do
request do
body do
string :title
end
end
response do
body do
integer :id
string :title
end
end
end# app/controllers/api/v1/posts_controller.rb
def create
post = Post.create(contract.body) # { title: }
expose post # { id:, title: }
endRequest
query
For GET parameters:
action :search do
request do
query do
string :q
end
end
endbody
For POST/PATCH request body:
action :create do
request do
body do
object :post do
string :title
string :body
end
end
end
endResponse
body
Define the response shape:
action :show do
response do
body do
integer :id
string :title
datetime :created_at
end
end
endno_content!
For actions that return HTTP 204 No Content:
action :destroy do
response do
no_content!
end
endGenerated output:
- OpenAPI:
204 No Content(nocontentkey) - TypeScript:
never - Zod:
z.never()
INFO
The standard adapter uses no_content! by default for destroy actions. Override with replace: true if you need to return data.
Raises
Declare which errors an action can raise:
action :show do
raises :not_found, :forbidden
end
action :create do
raises :unprocessable_entity
endThese appear in generated OpenAPI exports as possible error responses. Raises can also be declared at the API level for errors common to all endpoints.
Declaration Merging
Actions support declaration merging. When you define an action that already exists, the definitions combine rather than replace.
INFO
This is the same merge behavior used for types. The concept applies consistently across Apiwork.
The Concept
// TypeScript interface merging
interface CreateRequest {
body: { title: string };
}
interface CreateRequest {
body: { priority: string };
}
// Result: { title: string; priority: string }Apiwork works the same way:
# Representation-generated (via adapter)
action :create do
request do
body do
string :title
decimal :amount
string :status
end
end
end
# Your definition (in contract)
action :create do
request do
body { string :priority }
end
end
# Result: body has ALL params (title, amount, status, priority)Deep Merge
Merging happens at every level of the hierarchy:
| Level | Merge behavior |
|---|---|
action | Same action name merges request/response |
request / response | Merge query/body definitions |
query / body | Merge params |
Nested param | Merge nested shapes recursively |
Example of deep merge:
# First definition
action :create do
request do
body do
object :invoice do
string :title
end
end
end
end
# Second definition (merges)
action :create do
request do
body do
object :invoice do
string :priority # Added to existing :invoice
end
end
end
end
# Result: :invoice has BOTH :title AND :priorityWith representation
When using representation, the adapter auto-generates request and response shapes from the representation attributes. Custom definitions merge with these:
class InvoiceContract < Apiwork::Contract::Base
representation # Generates: body { string :title; decimal :amount; ... }
action :create do
request do
body do
object :invoice do
string :priority # Added to representation params
end
end
end
end
endOpting Out with replace
replace: true includes only the explicitly declared params, not merged with auto-generated:
action :create do
request replace: true do
body do
object :invoice do
string :title # ONLY this, no representation params
end
end
end
endreplace: true can be used on:
request replace: true— replace entire requestresponse replace: true— replace entire response
TIP
Use replace: true for destroy actions that return metadata instead of the resource, custom endpoints with completely different shapes, or minimal requests that only accept specific params.
INFO
raises has no replace: option. Error declarations from the adapter (like :unprocessable_entity) are always included.
Metadata
Document actions with metadata fields:
action :index do
summary "List all posts"
description "Returns a paginated list of posts"
tags :posts, :public
end
action :create do
summary "Create a post"
deprecated!
operation_id "createPost"
raises :unprocessable_entity
request do
body do
string :title
end
end
endMetadata Fields
| Field | Description |
|---|---|
summary | One-line description, shows in endpoint lists |
description | Longer description, supports markdown |
tags | Action-specific tags for grouping |
deprecated | Marks the action as deprecated |
operation_id | Explicit operation ID for OpenAPI |
Translations
Summaries and descriptions can be translated. When defined in locale files instead of inline, they change with I18n.locale.
See also
- Contract::Action reference — all action methods and options