Adding a new resource to the public API
Walks through adding a resource end-to-end. The chain is:
Sync is automated through make sync and codegen — when the spec changes, raff-go is regenerated and the consumers rebuild against it.
Prerequisites
- The backend gateway already implements the endpoints you’re about to expose. Validate first with
curl against https://api.rafftechnologies.com/api/v1/... and a real API key.
- You’re working from the workspace root
raff_technologies/ where all four repos live side by side.
1. Edit the OpenAPI spec
Edit docs/api-reference/openapi.yaml:
- Add a tag for the resource (e.g.
Volumes) under tags: if it doesn’t exist.
- Add each operation under
paths: with a unique operationId. Use camelCase verb-resource form: listVolumes, createVolume, getVolume, resizeVolume.
- Reference request/response schemas with
$ref: '#/components/schemas/Xxx'. Avoid inline type: object definitions — they generate ugly anonymous structs in Go.
- Add the resource and request/response schemas under
components/schemas/.
Spec quality rules that affect codegen:
required: arrays on response schemas matter. Fields marked required generate as non-pointer Go types (Name string instead of *string). Mark every field that is always present in API responses as required. Optional/nullable fields stay non-required.
- Avoid schemas named
<OperationID>Response — codegen generates a wrapper type with that exact name and you’ll get a name collision. If you need a result schema, name it XxxResult.
- Use enums via
enum: for fixed value sets (e.g. region: enum: [us-east]). Codegen produces a typed string for each.
Lint the spec locally:
Or via the top-level Makefile:
2. Add Mintlify navigation and MDX stubs
Add the new operation pages in docs/docs.json under the appropriate group, and create matching MDX stubs in docs/api-reference/<resource>/. Each stub needs only the frontmatter:
Mintlify auto-renders the page from openapi.yaml.
3. Scaffold the Go side
From the workspace root:
This creates:
raff-go/<resource>s.go — thin idiomatic wrapper using generated spec.* types
raff-cli/internal/commands/<resource>.go — Cobra command group
terraform-provider-raff/internal/provider/resource_<resource>.go — Terraform resource
Each file has TODO markers for the resource-specific bits (schema attributes, flag wiring, etc.).
4. Regenerate raff-go from the spec
This runs go generate in raff-go/spec/, which calls oapi-codegen against docs/api-reference/openapi.yaml and writes raff-go/spec/spec.gen.go. Commit the regenerated file along with the spec change — they belong together.
5. Resolve the TODO markers
Open the three scaffolded files and fill in the resource-specific code:
- raff-go: confirm the type aliases match the generated names (the spec might generate
CreateVolumeRequest but if your operationId is createVolume, the request body alias is CreateVolumeJSONRequestBody = CreateVolumeRequest). Implement any non-CRUD methods (Attach, Detach, Resize, etc.).
- raff-cli: define the flags, build the request from flags, render output (table + JSON).
- terraform-provider-raff: define the schema (required, optional, computed), implement Create/Read/Update/Delete, write
setResourceXxxState to copy the API response into Terraform state.
6. Wire each piece into its entry point
raff-go/raff.go — add the field on Client, initialize it in New:
raff-cli/internal/commands/root.go — rootCmd.AddCommand(newVolumeCmd())
terraform-provider-raff/internal/provider/provider.go — ResourcesMap["raff_volume"] = resourceVolume()
7. Verify end-to-end
From the workspace root:
This regenerates raff-go, then builds + vets + tests raff-cli and terraform-provider-raff against it. If anything fails, fix and re-run.
Smoke-test the new commands against prod:
For Terraform, build the provider locally and use a dev override:
Then run terraform plan against a config that uses the new resource.
8. Update the changelog
Add a dated entry to docs/api-reference/changelog.mdx:
Use the emoji legend: 🟢 Added, 🔵 Updated, 🟡 Deprecated, 🔴 Removed, 🟣 Fixed.
9. Commit
The spec, regenerated SDK, CLI commands, and Terraform resource all live in different repos. Commit each separately with a matching message, and reference the spec change PR/commit in the downstream commits. The order is:
docs — spec + MDX stubs + changelog
raff-go — regenerated spec.gen.go + new wrapper file
raff-cli — new command file + root.go registration
terraform-provider-raff — new resource file + provider.go registration
CI drift guard
Each Go repo has a make verify (or the top-level make verify-no-drift) target that:
- Runs
go generate in raff-go/spec/
- Fails if
git status shows any change to spec.gen.go
This catches the case where someone edits the spec but forgets to regenerate. Wire it into the raff-go CI check on every PR. (Note: in CI, raff-go needs the docs repo checked out at ../docs for the generate step to find the spec.)
Reference
- Spec:
docs/api-reference/openapi.yaml
- Generated client:
raff-go/spec/spec.gen.go — never edit by hand
- Codegen config:
raff-go/spec/oapi-codegen.yaml
- Templates:
scripts/templates/*.tmpl
- Scaffolding:
scripts/new-resource.sh
- Top-level orchestrator:
Makefile
Last modified on May 6, 2026