Skip to content

Openapi v3 parity - #5496

Draft
sethboyles wants to merge 20 commits into
mainfrom
openapi-v3-parity-graphql-compatible
Draft

sethboyles wants to merge 20 commits into
mainfrom
openapi-v3-parity-graphql-compatible

Conversation

@sethboyles

@sethboyles sethboyles commented Sep 30, 2026 •

Copy link
Copy Markdown
Member
Change Endpoints (errors) Why it's correct
Removed endpoints that don't exist PATCH /v3/apps/{guid}/builds, PUT /v3/tasks/{guid}/actions/cancel (2) Neither route exists in config/routes.rb. Task cancel is POST /v3/tasks/{guid}/actions/cancel, plus the legacy PUT /v3/tasks/{guid}/cancel, both still documented.
Removed success statuses CC never returns POST /v3/buildpacks/{guid}/upload 200, POST /v3/droplets/{guid}/upload 200, POST /v3/packages/{guid}/upload 202, POST /v3/spaces/{guid}/manifest_diff 202, PATCH /v3/routes/{guid}/relationships/space 204 (5) The buildpack and droplet uploads always queue a job and return 202 with a Location header. Package upload returns 200, manifest_diff returns 201, and route transfer returns 200. The statuses that remain are the ones the controllers render.
Removed response bodies that are never sent POST /v3/deployments/{guid}/actions/cancel and /continue 200 application/json, DELETE /v3/droplets/{guid} 202 application/json, GET /v3/droplets/{guid}/download 200 text/html (4) Cancel and continue end with head :ok (no body), and droplet delete with head :accepted plus Location. Download sends the blob or redirects to the blobstore; it never returns HTML.
Removed application/json request bodies from bits uploads POST /v3/droplets/{guid}/upload, POST /v3/packages/{guid}/upload (2) Both endpoints take a multipart form (bits, and for packages also resources), per the v3 docs. A JSON body isn't accepted.
Removed a request enum value CC rejects PATCH /v3/builds/{guid} state: STAGING (1) BuildUpdateMessage only accepts BuildModel::FINAL_STATES (STAGED, FAILED); STAGING gets a 422. STAGING is still listed as a response state.
Request data is not nullable PATCH /v3/routes/{guid}/relationships/space (1) RouteTransferOwnerMessage validates data with presence: true, allow_nil: false, so null gets a 422.
Added a response enum value the API already returns GET /v3/processes/{guid}/process_instances state: STOPPING (1) InstancesStatsReporter reports stopping instances as STOPPING when app_instance_stopping_state is enabled (it is by default), and the v3 docs list it. The previous enum was incomplete, so clients could already receive a value outside it.
Nullable to-one relationship data GET/PATCH /v3/apps/{guid}/relationships/current_droplet, GET/PATCH /v3/spaces/{guid}/relationships/isolation_segment (8), GET/PATCH /v3/organizations/{guid}/relationships/default_isolation_segment (2) RelationshipToOne.data was type: [object, "null"] next to a $ref to Relationship (type: object). In OpenAPI 3.1 both apply, so data: null (e.g. a space with no isolation segment) failed validation. It's now anyOf: [Relationship, null], which accepts the same values the schema was meant to. oasdiff reports the new anyOf (and the moved type) as breaking even though the accepted values are unchanged. The org default-isolation-segment schema has the same null member under oneOf, already the case before this PR.

@sethboyles
sethboyles marked this pull request as draft September 30, 2026 21:52
sethboyles and others added 20 commits September 30, 2026 16:45
The 42 tag descriptions were one-line summaries written for the spec, and
several were inaccurate ("Service plans are plans for a service."). Replace
them with the prose the v3 reference docs use for each resource, and add
tags for the four resources that the v3 docs document separately but the
spec had folded into their parents: App Features, Space Features, Service
Plan Visibility and Route Policies.

Language derived from (40 of 42 are byte-identical to their source):
  docs/v3/source/includes/resources/*/_header.md
  docs/v3/source/includes/resources/*/_header.md.erb

Resource-level reference material folded into the matching tag, so it is
rendered once rather than repeated on every operation:
  docs/v3/source/includes/resources/app_features/_supported_features.md.erb
  docs/v3/source/includes/resources/app_features/_service_binding_files.md.erb
  docs/v3/source/includes/resources/feature_flags/_flags.md.erb
  docs/v3/source/includes/resources/roles/_valid_roles.md.erb
  docs/v3/source/includes/resources/service_brokers/_jobs.md.erb
  docs/v3/source/includes/resources/service_offerings/_visibility.md.erb
  docs/v3/source/includes/resources/service_plan_visibility/_visibility_types.md.erb
  docs/v3/source/includes/resources/audit_events/_header.md.erb (event type list)

Two places deviate from verbatim, both deliberate:
- Feature Flags: _flags.md.erb holds the table as an ERB loop, so it is
  rendered here as a plain markdown table. The hash_based_routing row is
  new -- the flag is in FeatureFlag::DEFAULT_FLAGS (app/models/runtime/
  feature_flag.rb) but is missing from _flags.md.erb.
- Service Offerings: _visibility.md.erb is appended after the header prose.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
info.description was a short stub with two typos. The v3 reference docs
carry ~80KB of conceptual documentation that the spec had no equivalent
for at all, and existing parameter descriptions already link into it
(for example "[relational operators](#relational-operators)" in
components/parameters/CreatedAts.yaml). Port it verbatim so those anchors
resolve and the concepts are documented.

Language derived from, in this order:
  docs/v3/source/includes/introduction/_introduction.md
  docs/v3/source/includes/concepts/_*.md.erb           (19 files)
  docs/v3/source/includes/upgrade_guide/**/*.md        (38 files)

The result is byte-for-byte reproducible from those files; the only text
that is not from the v3 docs is the leading note that this spec is
experimental. Mechanical conversions applied to make Slate markup render
as plain markdown:
  <aside class="notice|warning">  ->  > **Note:|Warning:** blockquote
  fenced single-line captions       ->  bold caption
  <h3 id="...">Title</h3>          ->  ### Title
  HTML comments                    ->  removed

Renders as three top-level sections (Introduction, Concepts, Upgrade
Guide) with 26 subsections.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
RelationshipToOne declared `data` as `$ref: Relationship.yaml` with a
sibling `type: [object, "null"]`. Under JSON Schema 2020-12 both apply, so
the $ref's `type: object` won and a legitimate `data: null` response --
an organization role has no space, for example -- failed validation.
`redocly lint` flagged this on the role examples. Replaced with an explicit
anyOf of the $ref and `null`. That is equivalent to oneOf here, since an
object and null never overlap, and it lets generators such as GraphQL Mesh
keep producing the existing Relationship type.

IncludedResources had `additionalProperties: false` but was missing three
keys the API really does return, so responses using them could not
validate: route_policies (RouteShowMessage/RoutesListMessage accept
include=route_policies), service_plans and service_brokers (emitted by the
service offering and service plan `fields` decorators).

Note that docs/v3/source/includes/concepts/_includes.md.erb omits
route_policies from its table of resources with includes, so this follows
app/messages/routes_list_message.rb rather than the docs.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds the permitted-roles list to all 18 operations, the query parameters
and filter wording the v3 docs document, per-field descriptions on the app
object (state values, both relationships, metadata, links) and the app
feature, ssh_enabled, env, environment variables and permissions objects,
and response bodies plus examples for the app feature endpoints, which had
none.

Language derived from:
  docs/v3/source/includes/resources/apps/
  docs/v3/source/includes/resources/app_features/
  docs/v3/source/includes/api_resources/_apps.erb
  docs/v3/source/includes/api_resources/_app_features.erb
  docs/v3/source/includes/api_resources/_app_ssh_enabled.erb

Also fixes, following the implementation rather than the spec:
- AppFeatureList used `$ref: '#/components/schemas/...'` pointers, which do
  not resolve in the split layout; now relative file refs.
- AppUpdateRequestBody accepted environment_variables; AppUpdateMessage
  does not.
- CF-FeatureDisabled moved from 422 to 403, per the v3 docs.
- The stop example reported state STARTED.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- `App.relationships` stays a `$ref` to Relationships.yaml, with the
  relationship keys listed in its description. Typing it turns an open
  object into a fixed type and changes how the shared RelationshipToOne
  types get named.
- The object-valued `default` on `environment_variables` is described in
  text instead; object defaults on request properties cannot be
  represented as GraphQL default values and break schema printing.
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties are stated in the property descriptions rather
  than the schema; CC already rejects invalid requests with a 422.
  Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Bodies that already list their properties get no extra
  `type: object`. Adding these to the schema would change existing
  requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 13 operations and the embed, label_selector,
created_ats, updated_ats, guids and types parameters the v3 docs document.
The process stats object grew from 10 loosely typed fields to all 22
documented ones, and readiness health checks now have their own schema
rather than aliasing the health check schema, since their defaults and
wording differ.

Language derived from:
  docs/v3/source/includes/resources/processes/
    _object, _health_check_object, _readiness_health_check_object,
    _process_instance_object, _stats_object and the endpoint files
  docs/v3/source/includes/api_resources/_processes.erb

Also fixes:
- ProcessList used `allOf: [Pagination, ...]`, which flattened the
  pagination fields to the top level instead of nesting them under
  `pagination`.
- The process_instances description claimed the response contains index,
  state and uptime; ProcessInstancesPresenter returns `since`.

Note: PATCH /v3/apps/{guid}/processes/{type} is kept because it is real
(config/routes.rb), even though the Definition block in
docs/v3/source/includes/resources/processes/_update.md.erb omits it.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- ProcessList keeps `resources` in the second `allOf` entry, now next to
  a proper `pagination` property. Dropping the `allOf` renames every
  type generated under `resources`.
- `data.timeout` on the readiness health check is kept as a deprecated
  property rather than removed, since existing clients select it; it is
  never returned.
- The ProcessScaleRequestBody schema has a `title`, so generators keep
  the name they derived when the body was defined inline.
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Bodies that already list their properties get no extra
  `type: object`. Adding these to the schema would change existing
  requests and generated input types.
- `since` stays a `number`, which already covers the integer values
  returned.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 15 operations, route options (hash-based
routing) to the object and to the create and update bodies, and every
documented destination field. Destinations now use a request-shaped schema
for writes so the read-only fields are not accepted on input.

Language derived from:
  docs/v3/source/includes/resources/routes/
    _object, _destination_object, _route_options_object and the endpoint files
  docs/v3/source/includes/api_resources/_routes.erb

Also fixes, following the implementation:
- The `protocols` list filter did not exist in RoutesListMessage and would
  have been rejected; page/per_page on the destinations list are rejected
  by NoAdditionalParamsValidator.
- RouteDestinationList declared a `pagination` block that
  RouteDestinationsPresenter never emits, and the destination links were
  self/route rather than destinations/route.
- Transfer route ownership is documented as 204 in _transfer_route_ownership
  .md.erb but the controller renders 200 with {"status":"ok"} (see
  spec/request/routes_spec.rb).
- The v3 hash_balance example value is rejected by RouteOptionsMessage.
- Removes a duplicate /v3/spaces/{guid}/routes block; the registry points
  that path at paths/Spaces.yaml.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- `Route.relationships` stays a `$ref` to Relationships.yaml, with the
  relationship keys listed in its description. Typing it turns an open
  object into a fixed type and changes how the shared RelationshipToOne
  types get named.
- In RouteDestinationRequest, `app.guid` and `protocol` stay plain
  strings, with the UUID format and the valid protocols in their
  descriptions, so clients that pass strings keep working.
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties and length and range limits are stated in the
  property descriptions rather than the schema; CC already rejects
  invalid requests with a 422. Request bodies are not marked `required`,
  since CC treats a missing body as `{}`. Adding these to the schema
  would change existing requests and generated input types.
- Response values that were previously undeclared stay listed in the
  description rather than becoming a new `enum`.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 17 operations, including the per-role
qualifiers on the security group endpoints and the note that a Space
Manager cannot change `suspended`. Adds the `suspended` field, the full
user and security group filter sets, and descriptions for every
relationship and link.

Language derived from:
  docs/v3/source/includes/resources/spaces/
  docs/v3/source/includes/resources/space_features/
  docs/v3/source/includes/resources/manifests/_apply.md
  docs/v3/source/includes/resources/manifests/_create_diff.md
  docs/v3/source/includes/api_resources/_spaces.erb
  docs/v3/source/includes/api_resources/_space_features.erb

Also fixes, following the implementation:
- The features list returned the single-feature schema instead of a list,
  and its example invented a diego_docker space feature.
- manifest_diff was modelled as 201/202 with a Location header; it returns
  201 with a {"diff": [...]} body.
- The usage summary link is `space`; the v3 example says `organization`,
  but SpaceUsageSummaryPresenter emits `space`.

Deletes the unreferenced components/requestBodies/SpaceCreate.yaml;
SpaceCreateRequestBody.yaml is the one the paths use.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties are stated in the property descriptions rather
  than the schema; CC already rejects invalid requests with a 422.
  Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Bodies that already list their properties get no extra
  `type: object`. Adding these to the schema would change existing
  requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 12 operations and models the managed and
user-provided variants properly: create is a discriminated oneOf on `type`
with per-type required fields, update is an anyOf, and the three `fields`
parameters are now deepObject with the allowed keys enumerated rather than
bare objects. Adds last_operation, maintenance_info and
broker_provided_metadata as described sub-schemas.

Language derived from:
  docs/v3/source/includes/resources/service_instances/
  docs/v3/source/includes/api_resources/_service_instances.erb

Also fixes: syslog_drain_url and route_service_url were on the managed
instance schema, but ServiceInstancePresenter#hash_additions_managed does
not emit them and the v3 object table lists them as user-provided only.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- The service instance create and update bodies also declare the merged
  set of fields directly, next to the existing `oneOf`/`anyOf`, so
  generators that cannot express unions of input objects still get every
  field. `syslog_drain_url` and `route_service_url` stay plain strings.
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties are stated in the property descriptions rather
  than the schema; CC already rejects invalid requests with a 422.
  Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Adding these to the schema would change existing
  requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 13 operations, restores the multi-paragraph
prose on create, delete and parameters, and replaces the bare `type:
object` responses on the details and parameters endpoints with real
schemas. Adds the `strategy` field to credential binding creates and the
missing `parameters` links.

Language derived from:
  docs/v3/source/includes/resources/service_credential_bindings/
  docs/v3/source/includes/resources/service_route_bindings/
  docs/v3/source/includes/api_resources/_service_credential_bindings.erb
  docs/v3/source/includes/api_resources/_service_route_bindings.erb
The details response shape follows
app/presenters/v3/service_credential_binding_details_presenter.rb.

Also fixes, following the implementation:
- The credential binding `type` filter is a single value with an inclusion
  check in ServiceCredentialBindingListMessage, not a list.
- A 201 create only happens for user-provided instances, which cannot have
  key bindings, so it is narrowed to the app binding schema.
- Route binding last_operation is emitted by the presenter but undocumented
  in v3; it is included and marked nullable.
- The example GUIDs in both _service_*_bindings.erb files are malformed
  (non-hex characters) and the route binding relationship GUIDs are swapped
  relative to their own links; repaired here.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- ServiceCredentialBindingDetails stays an open object with its keys
  documented in the description; the content is broker-defined and
  clients read it as free-form JSON.
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Bodies that already list their properties get no extra
  `type: object`. Adding these to the schema would change existing
  requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the v3 docs

The service offering and service plan schemas were near-empty stubs whose
only fields (service_broker_guid, service_offering_name) do not exist in
the API. Both are rewritten from the v3 object tables: available, free,
tags, requires, shareable, documentation_url, costs, maintenance_info,
broker_catalog with its features block, schemas, visibility_type and the
relationships. Adds permitted roles to all 17 operations, the
broker_catalog_ids filter, and deepObject `fields` parameters.

Language derived from:
  docs/v3/source/includes/resources/service_brokers/
  docs/v3/source/includes/resources/service_offerings/
  docs/v3/source/includes/resources/service_plans/
  docs/v3/source/includes/resources/service_plan_visibility/
  docs/v3/source/includes/api_resources/_service_brokers.erb
  docs/v3/source/includes/api_resources/_service_offerings.erb
  docs/v3/source/includes/api_resources/_service_plans.erb
  docs/v3/source/includes/api_resources/_service_plan_visibility.erb

Broker authentication is extracted to its own write-only schema, shared by
the create and update bodies, and is required on create per
ServiceBrokerCreateMessage. The four visibility operations move to the new
Service Plan Visibility tag. The plan `links.space` entry is confirmed
against app/presenters/v3/service_plan_presenter.rb.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Input enums and formats are stated in the property descriptions rather
  than the schema; CC already rejects invalid requests with a 422.
  Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Bodies that already list their properties get no extra
  `type: object`. Adding these to the schema would change existing
  requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 13 operations, the copy endpoints, the
relationships.app field that was missing from the droplet schema entirely,
and per-field descriptions for state, checksum, buildpacks, image, error,
execution_metadata, process_types and the bits/docker data blocks.

Language derived from:
  docs/v3/source/includes/resources/droplets/
  docs/v3/source/includes/resources/packages/
  docs/v3/source/includes/api_resources/_droplets.erb
  docs/v3/source/includes/api_resources/_packages.erb

Copying is modelled as an optional source_guid query parameter on the
existing POST operations rather than as separate paths. The spec
previously carried a `/v3/packages?source_guid={source_guid}` path key,
which is not valid OpenAPI -- a path template cannot contain a query
string -- and it was never registered, so the endpoint was invisible.

Also fixes, following the implementation:
- Droplet upload declared a `resources` part and a JSON variant;
  DropletUploadMessage accepts only bits.
- Droplet upload returns 202 only, package upload 200 only (the
  controllers' render calls), not both.
- Droplet delete's 202 carried a Job body; the controller does head
  :accepted.
- DropletCreateMessage does not accept metadata.
- Package download now documents its 200 (streamed from a local blobstore)
  alongside the 302 (redirect to a remote one), per
  BlobDispatcher#send_or_redirect -- so the operation-2xx-response entry in
  .redocly.lint-ignore.yaml is no longer needed and is removed.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- `Package.relationships` stays a `$ref` to Relationships.yaml, with the
  relationship keys listed in its description. Typing it turns an open
  object into a fixed type and changes how the shared RelationshipToOne
  types get named.
- The object-valued `default` on `process_types` and `data` is described
  in text instead; object defaults on request properties cannot be
  represented as GraphQL default values and break schema printing.
- The PackageUpdateRequestBody schema has a `title`, so generators keep
  the name they derived when the body was defined inline.
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties are stated in the property descriptions rather
  than the schema; CC already rejects invalid requests with a 422.
  Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Adding these to the schema would change existing
  requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 8 operations, including the Build State Updater
component role note, the staging_memory_in_mb, staging_disk_in_mb and
staging_log_rate_limit_bytes_per_second create fields, the buildpack
lifecycle filter, and v3's defaults for buildpack create (stack, position,
lifecycle, enabled, locked).

Language derived from:
  docs/v3/source/includes/resources/builds/
  docs/v3/source/includes/resources/buildpacks/
  docs/v3/source/includes/api_resources/_builds.erb
  docs/v3/source/includes/api_resources/_buildpacks.erb

Also fixes, following the implementation:
- PATCH /v3/apps/{guid}/builds does not exist; config/routes.rb has only
  GET on that path. Operation removed.
- Buildpack upload returns 202 only and accepts only a `bits` part
  (BuildpackUploadMessage); the invented 200 and `resources` part are gone.
- Build had a `checksum` property the presenter never emits, and BuildUpdate
  had staging_* fields BuildUpdateMessage rejects.
- POST /v3/builds now references BuildCreateRequestBody, which existed but
  was orphaned while the operation duplicated it inline.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- `Build.relationships` stays a `$ref` to Relationships.yaml, with the
  relationship keys listed in its description. Typing it turns an open
  object into a fixed type and changes how the shared RelationshipToOne
  types get named.
- The BuildCreateRequestBody schema has a `title`, so generators keep
  the name they derived when the body was defined inline.
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties and input enums and formats are stated in the
  property descriptions rather than the schema; CC already rejects
  invalid requests with a 422. Adding these to the schema would change
  existing requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Deployments gain permitted roles on all 6 operations plus the fields the v3
object table documents and the spec lacked: status.details.error, the
canary step counters, and descriptions and enums for status.value,
status.reason, strategy and all six options. Revisions gain the processes
and sidecars snapshots and `deployable`.

Language derived from:
  docs/v3/source/includes/resources/deployments/
  docs/v3/source/includes/resources/revisions/
  docs/v3/source/includes/api_resources/_deployments.erb
  docs/v3/source/includes/api_resources/_revisions.erb

Also fixes, following the implementation:
- Cancel and continue return no body (the controllers call head :ok); the
  spec claimed a Deployment body.
- The revision `droplet` field is a bare {guid} object, not a relationship.
- Revision had an `enabled` property that does not exist.
- last_healthy_at is surfaced by the presenter as
  status.details.last_successful_healthcheck.

Note: docs/v3/source/includes/resources/deployments/_cancel.md.erb and
_continue.md.erb contain no prose, so the preconditions described on those
two operations are written from DeploymentCancel/DeploymentContinue
(cancelable? and continuable?) and docs/v3/zero_downtime_deployments.md
rather than copied from the endpoint docs.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- `Deployment.relationships` stays a `$ref` to Relationships.yaml, with
  the relationship keys listed in its description. Typing it turns an
  open object into a fixed type and changes how the shared
  RelationshipToOne types get named.
- `enabled` on the revision is kept as a deprecated property (never
  returned; use `deployable`) rather than removed, since existing
  clients select it.
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties and length and range limits are stated in the
  property descriptions rather than the schema; CC already rejects
  invalid requests with a 422. Adding these to the schema would change
  existing requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 12 operations, including the user_org_creation
flag caveat on create and the per-role notes on the quota list. Every leaf
of the nested apps, services, routes and domains quota objects now carries
its v3 description and the `null means unlimited` semantics, and the usage
summary and default domain/isolation segment endpoints have modelled
response shapes instead of bare objects.

Language derived from:
  docs/v3/source/includes/resources/organizations/
  docs/v3/source/includes/resources/organization_quotas/
  docs/v3/source/includes/api_resources/_organizations.erb
  docs/v3/source/includes/api_resources/_organization_quotas.erb
The usage summary leaves follow
app/presenters/v3/organization_usage_summary_presenter.rb, since v3
documents no field table for them.

Also fixes, following the implementation:
- OrganizationQuota had `metadata` and a links.organizations entry that
  OrganizationQuotaPresenter does not emit and the update message rejects.
- OrganizationQuotaList used `allOf: [Pagination, ...]`, flattening the
  pagination fields to the top level.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- OrganizationQuotaList keeps `resources` in the second `allOf` entry,
  now next to a proper `pagination` property. Dropping the `allOf`
  renames every type generated under `resources`.
- Applying a quota to organizations keeps using RelationshipToMany,
  which already describes a list of guids under `data`.
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties are stated in the property descriptions rather
  than the schema; CC already rejects invalid requests with a 422.
  Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Adding these to the schema would change existing
  requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 16 operations. Role creation now models both
documented shapes -- by user guid, and by username plus origin -- with a
oneOf over the organization and space relationships. User creation is a
strict oneOf (UAA guid, or username plus origin). Space quota nested
objects get every documented leaf, its default, and the `null means
unlimited` semantics.

Language derived from:
  docs/v3/source/includes/resources/roles/
  docs/v3/source/includes/resources/users/
  docs/v3/source/includes/resources/space_quotas/
  docs/v3/source/includes/api_resources/_roles.erb
  docs/v3/source/includes/api_resources/_users.erb
  docs/v3/source/includes/api_resources/_space_quotas.erb
The valid role types are enumerated from
docs/v3/source/includes/resources/roles/_valid_roles.md.erb, whose table is
in the Roles tag description rather than repeated here.

Also fixes, following the implementation:
- label_selector was offered on the roles and space quota lists; neither
  list message supports it.
- SpaceQuota carried `metadata`, but SpaceQuotaUpdateMessage is not a
  MetadataBaseMessage.
- The user update body exposed username, presentation_name, origin and
  links; UserUpdateMessage allows only metadata.
- Role examples contained a `user_group` relationship RolePresenter never
  emits.
- The v3 space quota list example says relationships.organizations; the
  presenter emits `organization`.
- Wires up schemas/UserCreate.yaml and schemas/SpaceQuotaUpdate.yaml, both
  of which existed but were orphaned.
- Removes a path with a trailing slash
  (/v3/space_quotas/{guid}/relationships/spaces/{space_guid}/) that matched
  no route and was unregistered.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- The SpaceQuotaUpdate schema has a `title`, so generators keep the name
  they derived when the body was defined inline.
- UserCreate declares the merged fields next to its `oneOf`, with a
  `title`, so generators that cannot express unions of input objects
  still get every field.
- Applying a quota to spaces keeps using RelationshipToMany, which
  already describes a list of guids under `data`.
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties are stated in the property descriptions rather
  than the schema; CC already rejects invalid requests with a 422.
  Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Adding these to the schema would change existing
  requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The security group rule object is extracted into its own schema with every
documented field, including `log`, which was missing everywhere, and the
protocol enum, the destination notes about comma-delimited lists and
icmpv6, and the -1..255 range on type and code. Stacks gain `state` and
`state_reason`. Permitted roles added to all 15 operations, and the
multi-paragraph prose on update, bind and unbind is restored rather than
flattened to one line.

Language derived from:
  docs/v3/source/includes/resources/security_groups/
  docs/v3/source/includes/resources/stacks/
  docs/v3/source/includes/api_resources/_security_groups.erb
  docs/v3/source/includes/api_resources/_stacks.erb
  docs/v3/source/includes/api_resources/examples/*security_group*.json

Also fixes, following the implementation:
- PATCH and DELETE on /v3/spaces/{guid}/staging_security_groups do not
  exist; config/routes.rb has GET only. Those operations are removed, along
  with two duplicate /v3/spaces/... path items whose registry entries point
  at paths/Spaces.yaml. This also resolves the duplicate updateSecurityGroup
  and deleteSecurityGroup operationIds.
- running_spaces and staging_spaces were top-level properties; they are
  to-many relationships.
- Security groups have no metadata support (no label model, and
  SecurityGroupPresenter emits none), so the metadata property is removed
  from all three security group schemas.
- SecurityGroup.links only ever contains self.
- The guids and names filters were described as case-insensitive; both the
  docs and the code say case-sensitive.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties, length and range limits and input enums and
  formats are stated in the property descriptions rather than the
  schema; CC already rejects invalid requests with a 422. Request bodies
  are not marked `required`, since CC treats a missing body as `{}`.
  Bodies that already list their properties get no extra `type: object`.
  Adding these to the schema would change existing requests and
  generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 12 operations and the documented task and
sidecar body fields. GET /v3/apps/{guid}/manifest now declares that it
returns YAML and references a Manifest schema carrying the manifest field
reference, which the spec had nowhere.

Language derived from:
  docs/v3/source/includes/resources/tasks/
  docs/v3/source/includes/resources/sidecars/
  docs/v3/source/includes/resources/manifests/_object.md.erb (field reference)
  docs/v3/source/includes/api_resources/_tasks.erb
  docs/v3/source/includes/api_resources/_sidecars.erb

Also fixes, following config/routes.rb:
- The cancel endpoints are POST /v3/tasks/{guid}/actions/cancel and
  PUT /v3/tasks/{guid}/cancel; the spec had PUT on the actions path. The
  PUT form is marked deprecated, as the v3 docs label it.
- Removes the two dead /v3/spaces/{guid}/... manifest path items from
  paths/Manifests.yaml; the registry points both at paths/Spaces.yaml,
  where they are defined.

Note: the task object table in
docs/v3/source/includes/resources/tasks/_object.md.erb names the field
log_rate_limit_per_second, but TaskPresenter emits
log_rate_limit_in_bytes_per_second; the spec follows the presenter.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Required properties are stated in the property descriptions rather
  than the schema; CC already rejects invalid requests with a 422.
  Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Bodies that already list their properties get no extra
  `type: object`. Adding these to the schema would change existing
  requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to every operation in these four resources, the job
state and operation wording with its POLLING note, the resource match
`mode` property and required fields, and the link lists both root endpoints
actually return -- the /v3 root was missing eight of them
(app_usage_events, audit_events, buildpacks, environment_variable_groups,
organization_quotas, resource_matches, service_usage_events, space_quotas).

Language derived from:
  docs/v3/source/includes/resources/jobs/
  docs/v3/source/includes/resources/resource_matches/
  docs/v3/source/includes/resources/admin/
  docs/v3/source/includes/resources/root/
  docs/v3/source/includes/api_resources/_jobs.erb
  docs/v3/source/includes/api_resources/_resource_matches.erb
  docs/v3/source/includes/api_resources/_root.erb

Also fixes:
- JobList used `$ref: '#/components/schemas/...'` pointers, which do not
  resolve in the split layout; now relative file refs.
- Both root endpoints get `security: []`, since the v3 docs state they
  require no authentication and the spec applies OAuth globally.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- Request bodies are not marked `required`, since CC treats a missing
  body as `{}`. Adding these to the schema would change existing
  requests and generated input types.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds permitted roles to all 7 operations, completes the audit event `types`
enum (26 values were missing, including the buildpack, organization_quota,
route_policy, space_quota and stack lifecycles), spells out the
[gt]/[gte]/[lt]/[lte] timestamp operator syntax, and fills in the service
usage event object, which previously had only `links`.

Language derived from:
  docs/v3/source/includes/resources/audit_events/
  docs/v3/source/includes/resources/app_usage_events/
  docs/v3/source/includes/resources/service_usage_events/
  docs/v3/source/includes/api_resources/_audit_events.erb
  docs/v3/source/includes/api_resources/_app_usage_events.erb
  docs/v3/source/includes/api_resources/_service_usage_events.erb
The audit event type list itself lives in the Audit Events tag description
rather than being repeated in the schema.

Also fixes:
- Both usage event list examples contained leaked ERB in their pagination
  hrefs (`https://api.example.org<%= path %>?...`).
- The app usage event schema had no links object at all.
- The purge endpoints return 200 only; both controllers render :ok.
- updated_ats is documented for the usage event lists but
  DisallowUpdatedAtsParamValidator rejects it, so it is not offered.

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ith v3

Adds permitted roles to the six authenticated operations, the missing
osbapi_version and rate_limits fields on /v3/info, and real schemas for
both info endpoints in place of inline stubs. The environment variable
group `name` path parameter had no description at all and is now
documented and enumerated as running or staging.

Language derived from:
  docs/v3/source/includes/resources/feature_flags/
  docs/v3/source/includes/resources/environment_variable_groups/
  docs/v3/source/includes/resources/info/
  docs/v3/source/includes/api_resources/_feature_flags.erb
  docs/v3/source/includes/api_resources/_environment_variable_groups.erb
  docs/v3/source/includes/api_resources/_info.erb
The info fields follow app/presenters/v3/info_presenter.rb. The flag list
itself is in the Feature Flags tag description.

/v3/info states "no authentication required" rather than a roles list,
matching the v3 docs, and declares a 429: unauthenticated requests are
rate limited (middleware/rate_limiter.rb, middleware/base_rate_limiter.rb).

Also fixes, following the implementation:
- The hash_based_routing flag is accepted by the name parameter; it exists
  in FeatureFlag::DEFAULT_FLAGS but not in the v3 flag table.
- EnvironmentVariableGroup.name was nullable; the presenter always returns
  running or staging.
- Response code sets trimmed to what these endpoints can actually return
  (/v3/info takes no auth, parameters or body, yet declared 400/403/404/
  409/422/502).

To keep code generated from the spec stable (GraphQL Mesh, for example,
derives type, field and argument names from its structure), this also:
- Query parameters keep their existing order, and `order_by` stays a
  plain string with the valid values in its description. Generators key
  argument handling off both.
- Response values that were previously undeclared stay listed in the
  description rather than becoming a new `enum`.

ai-assisted=yes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sethboyles
sethboyles force-pushed the openapi-v3-parity-graphql-compatible branch from 946356b to efba23e Compare September 30, 2026 22:47
@sethboyles sethboyles changed the title Openapi v3 parity graphql compatible Openapi v3 parity Oct 1, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant