Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This repository is split deliberately, by audience:
Root-level documents:

- **[plan-remaining.md](plan-remaining.md)** — everything specified but not built yet: test-mode User expiry, the first-party web pages, Phase 3, dedicated tenancy infrastructure, scale-out triggers, and the rules every change follows.
- **[remaining_manual_steps.md](remaining_manual_steps.md)** — one-time setup still to do by hand to go live: the Lambda quota, the superadmin password, SES production access, Stripe, and legal.
- **[`[bespoke]` issues](https://github.com/CompositeCode/substratalapps.com/issues?q=is%3Aissue+%22%5Bbespoke%5D%22+sort%3Acreated-asc)** — the one-time setup still to do by hand to go live, as numbered GitHub issues in the order to do them (#18–#28): the Lambda quota, redeploy, the superadmin password, alert emails, SES production access, Stripe, push credentials, registering Applications, the plan job, legal, and the optional domain transfer.
- **[infra/README.md](infra/README.md)** — the deployment architecture as built: AWS infrastructure (cost-capped, Aurora Serverless v2, Lambda, no VPC/NAT at Tier 0), the account setup, Stripe Billing, email, webhook and push delivery, audit archival, and local development.
- **[LICENSE](LICENSE)** — MIT.

Expand Down Expand Up @@ -187,7 +187,7 @@ For an urgent fix while `dev` holds unreleased work:

### One-time setup (Phase 0)

The steps above assume these exist. They're done once, by hand; what's still left of them is tracked in [remaining_manual_steps.md](remaining_manual_steps.md):
The steps above assume these exist. They're done once, by hand; what's still left of them is tracked as the numbered [`[bespoke]` issues](https://github.com/CompositeCode/substratalapps.com/issues?q=is%3Aissue+%22%5Bbespoke%5D%22+sort%3Acreated-asc):

1. A dedicated AWS account in `us-east-1` ([infra/README.md → AWS account & region](infra/README.md#aws-account--region)), with its default VPC in place (Aurora's subnets come from it; Lambda never joins a VPC), and a Route 53 hosted zone for `substratalapps.com`.
2. From your own machine, with admin credentials: `cd infra/terraform/bootstrap && terraform init && terraform apply -var billing_email=you@example.com`. This creates the **cost guardrails first** (warn and hard budgets with a circuit-breaker action, Cost Anomaly Detection, a billing alarm), then the state and artifact buckets, the GitHub OIDC provider, and the read-only `plan` and `deploy` roles. It's the only Terraform ever applied from a laptop.
Expand Down
3 changes: 2 additions & 1 deletion docs/api-reference/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ A single resource is returned as a bare JSON object — no envelope:
| `tenant_suspended` | 403 | The Tenant that owns the data being written (or the Application an app token is requested for) is `suspended` by the platform. See [Tenancy](../../domain-model/tenancy/#fields). |
| `destructive_operation_restricted` | 403 | A `restrict_destructive` API Key attempted a destructive operation — see [API Keys](../api-keys/#agent-keys--restrict_destructive). |
| `not_found` | 404 | Generic not-found for a path that matches no route. Resource pages define their own `<resource>_not_found` codes. |
| `method_not_allowed` | 405 | The path exists, but not for this method. The `Allow` header and `details.allowed` list the methods it does support. |
| `version_conflict` | 409 | `If-Match` didn't match the current `ETag`. |
| `idempotency_key_reused` | 409 | Same `Idempotency-Key`, different request body. |
| `idempotency_key_in_flight` | 409 | The first request with this `Idempotency-Key` is still being processed. Retry after a second. |
Expand Down Expand Up @@ -331,7 +332,7 @@ Versions are whole numbers, and `1` is the only one today. The URL never carries
- **Browsers:** `Substratal-Version` is allowed in CORS requests and exposed on CORS responses.
- **Changes.** Additive changes (new optional fields, new endpoints, new enum values) ship within a version. A breaking change ships as the next number, and the previous version keeps working for at least 6 months, with `Deprecation` and `Sunset` headers on every response to a request that names it. After its `Sunset` date it's retired, and requests naming it get `400 unsupported_api_version`. See [Non-Functional Requirements → Versioning](../../non-functional-requirements/#versioning) for what counts as breaking.
- **Not the webhook version.** A webhook subscription's `api_version` (a date, such as `2026-10-05`) fixes the shape of the payloads it receives, and is versioned separately. See [Webhooks → Payload versions](../webhooks/#payload-versions).
- **Generated clients:** `openapi.yaml` doesn't declare the header on each operation, so configure your client to send it as a default header.
- **In `openapi.yaml`:** every versioned operation declares the header as the shared `ApiVersion` parameter, and documents the `400` it returns without one, so generated clients know to send it. The exempt operations don't declare it.

## Timestamps

Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ What's changed in this specification over time. Every decision is written into t

---

## 2026-10-22 — Version header declared in `openapi.yaml`; 405 for a wrong method

- **`openapi.yaml` declares `Substratal-Version`.** Every versioned operation now carries the shared `ApiVersion` header parameter (`Substratal-Version`, enum `1`) and documents its `400` (`api_version_required`, `unsupported_api_version`). Generated clients learn the header from the spec instead of needing it configured by hand. `GET /health` and `POST /auth/oauth/token` don't declare it, because they're exempt. Nothing changes on the wire: the API already required the header ([Conventions → Versioning](../api-reference/conventions/#versioning)).
- **`405 method_not_allowed`.** Calling a path that exists with a method it doesn't support (for example `PUT /health`) now returns `405` with an `Allow` header and `details.allowed`, instead of the catch-all `404 not_found`. See [Conventions → Errors](../api-reference/conventions/#errors).

## 2026-10-21 — SCIM provisioning

An Organization's identity provider can invite and remove its members with SCIM 2.0. See [API Reference → SCIM](../api-reference/scim/). `openapi.yaml` is now at `1.0.0-draft.5`, with 121 operations.
Expand Down
Loading
Loading