Three copies of every field
The migration, the validator, and the response type usually live apart, and they disagree quietly.
A model with the useApi trait generates its own actions and routes, validated by the rules already declared on its attributes. What ships is a typed API, an OpenAPI spec that matches it, and a client that fails to compile when the contract changes.
The work that arrives whether you planned for it or not.
The migration, the validator, and the response type usually live apart, and they disagree quietly.
A hand-maintained spec is fiction within a sprint of the code moving.
Tokens, scopes, per-route rate limits, and CORS have to be right on every route, not most of them.
The second consumer is when v1 has to keep working while v2 exists.
All of it in the same install, typed against the rest of your application.
useApi on a model produces index, store, show, update, and destroy actions plus their routes, each overridable in app/Actions/.
Attribute rules validate the request, drive the factory, and describe the schema in the spec.
Prefixes, named routes, model binding, middleware groups, and per-route rate limits live in routes/.
buddy generate:openapi emits the spec from the routes that exist, so the reference cannot drift from the app.
API tokens with scopes, session cookies, and two-factor all come from the auth package.
The fetch client is generated against the same types, so a renamed field is a compile error in the consumer.
Every one of these ships in the same install. Follow one to see what it covers.
Buddy scaffolds, runs, and ships this the same way it does the rest of the app.
Projects of this shape usually grow into one of these.