- The OpenAPI 3.0 spec lives at
apispec/api.spec.json. All API contract changes start here. - The spec is served at runtime via
/api/entitlements/v1/openapi.json(read from disk, path configured viaOpenAPISpecPathconfig key). - Base URL for all endpoints:
/api/entitlements/v1/.
- Only the
seatstag endpoints use oapi-codegen. The/servicesand/complianceendpoints are hand-written. - Two config files in
controllers/drive generation:types.cfg.yaml— generatesapi/types.gen.go(models only, filtered toseatstag).server.cfg.yaml— generatesapi/server.gen.go(chi server + strict server + embedded spec, filtered toseatstag).
- Generator directives live as
//go:generatecomments at the top ofcontrollers/seats.go, not in a separategenerate.gofile. - Run
make generateorgo generate ./...to regenerate. Generated*.gen.gofiles are gitignored. - Generator version is pinned:
github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@v2.0.0. Do not change without coordinating. - The seat manager feature is disabled by default (
DisableSeatManagerdefaults totrueinconfig/main.go). Generated seat endpoints are only registered when this flag isfalse.
- Add the path and schemas to
apispec/api.spec.json. - Tag the operation with
seatsto include it in generation, or add a newinclude-tagsentry to both cfg files. - Run
make generate. - Implement the new method on
SeatManagerApito satisfyapi.ServerInterface.
SeatManagerApiimplementsapi.ServerInterface(compile-time check:var _ api.ServerInterface = &SeatManagerApi{}).- Registered via
api.HandlerFromMuxWithBaseURL(seatManagerApi, r.With(enforceIdentity), "/api/entitlements/v1")— identity enforcement is applied inline at registration, not as a separate middleware step. - Request/response types come from
apipackage (generated). Use pointer fields with thetoPtr[T]helper. - Query params arrive as generated
api.GetSeatsParamsstruct; applyfillDefaults()for nil optional fields. - Errors use
doError()which maps throughSeatsErrorMapperto produceapi.ErrorJSON responses.
- Registered as plain
http.HandlerFuncon the chi router inserver/routes.go. - Query params are parsed manually via
req.URL.Query().Get()helpers (filtersFromParams,boolFromParams). - Request/response types are defined in
types/package withjsonstruct tags usingsnake_case. - Errors use endpoint-specific helpers (
failOnDependencyError,failOnBadRequest,failOnComplianceError).
- All JSON field names use
snake_case(e.g.,is_entitled,account_username,subscription_id). - Query parameter names are
snake_casefor hand-written endpoints (e.g.,include_bundles,trial_activated) butcamelCasefor generated seats endpoints (e.g.,accountUsername,firstName,lastName). Follow whichever convention the endpoint style uses. - Enum values use
PascalCasefor status values (Active,Deprovisioned) andsnake_casefor sort fields. - Use
x-enum-varnamesin the spec to control generated Go constant names. - Array query params use
style: formwithexplode: false(comma-separated).
- Pagination uses
offset/limitpattern (not cursor-based). - Default limit: 10. Min: 1. Max: 1000. Default offset: 0. Min: 0.
- Paginated responses use
ListPagination(containsmeta.countandlinks.{first,previous,next,last}). - Pagination links follow the format
/api/entitlements/v1/seats/?limit=N&offset=M. lastlink is omitted when the upstream (AMS) does not provide total count.
Three distinct error shapes exist — use the correct one for context:
| Schema | When to use | Fields |
|---|---|---|
api.Error (generated) |
Seats endpoints | error, status, identifier, code, operation_id |
DependencyErrorResponse |
Upstream service failure | error.{dependency_failure, service, status, endpoint, message} |
RequestErrorResponse |
Bad request to entitlements | error.{status, message} |
Do not introduce new error shapes. Map upstream errors (AMS, BOP) through SeatsErrorMapper.
- Prefer setting
Content-Type: application/jsonbefore writing the response body. Note: the hand-written error helpers (failOnDependencyError,failOnBadRequest,failOnComplianceError) usehttp.Error(), which setsContent-Type: text/plain; charset=utf-8instead. New error helpers should set the header explicitly rather than relying onhttp.Error(). /servicessetsX-Entitlements-Degraded: trueandX-Entitlements-Degraded-Status: <code>when upstream calls fail but the request still returns 200 with degraded data.
- All authenticated routes use
identity.EnforceIdentityWithLoggermiddleware (fromplatform-go-middlewares/v2). - Identity is extracted via
identity.GetIdentity(req.Context()).Identity. - Service Accounts (
idObj.User == nil) are handled explicitly — they cannot perform org-admin actions or compliance screening. - Org-admin checks (
idObj.User.OrgAdmin) gate write operations on seats (POST, DELETE). - DELETE
/seats/{id}additionally verifies the subscription's AMS org matches the caller's org.
- Bundle definitions live in
bundles/bundles.yml(gitignored;bundles.example.ymlis committed). - Bundle YAML schema:
name,use_valid_acc_num,use_valid_org_id,use_is_internal,skus,eval_skus,paid_skus. - Adding a new bundle does NOT require spec changes — the
/servicesresponse is a dynamic map keyed by bundle name. - The
Serviceschema usesadditionalPropertiesreferencingServiceDetails, making it an open-ended map.
- Add path, parameters, and schemas to
apispec/api.spec.json. - If using codegen: tag appropriately, update cfg files if needed, run
make generate, implement interface. - If hand-written: add types to
types/package, add handler incontrollers/, register route inserver/routes.go. - Wrap route with
enforceIdentitymiddleware unless it is public (only/status,/metrics, and/api/entitlements/v1/openapi.jsonare unauthenticated). - Use existing error response shapes and helpers — do not create new ones.
- Controller tests use Ginkgo/Gomega (
controllers_suite_test.go). - Seats tests mock
ams.AMSInterfaceandbop.Bopinterfaces. - Subscription tests replace
GetFeatureStatus(package-level var) for mocking.