Keyring

Checkout

Moving a workspace onto a paid plan, through Stripe Checkout.

Generated from packages/api/src/billing/checkout.controller.ts by packages/docs/tools/generate-reference.mjs. Every example is a real exchange recorded against the control plane by packages/api/src/testing/reference-examples.spec.ts, with ids from that run. Credentials in the formats this product issues (kr_, krsk_, krses_, JWTs) and values under credential-named fields (password, *_secret, *_token, *_key, lookup_hash) are elided at record time; the recorded file and every page of this site are swept for credential shapes (kr_, krsk_, krses_, JWTs, URLs carrying a password, password fields) before either is accepted. Do not edit by hand: src/reference.spec.ts regenerates it and fails on a difference.

POST /v1/billing/checkout_session -- the one write on the billing plane, and the only thing in this repository that can move a workspace off the entry plan.

Three guards, each for a different reason:

  • assertLiveCredential, because a subscription carries no environment at all. A krsk_test_ key is the credential a vendor hands to CI, and "CI started a paid subscription" is the same class of mistake as "CI deleted the project holding every live key". env-boundary.spec.ts sweeps this route for it, from Fastify's own route table.
  • assertOwner, because buying is an owner's decision. It returns early for a machine principal by design, which is exactly why the line above it is not redundant.
  • the gateway being configured, because with no Stripe secret key there is nothing to check out against. Unset closes the endpoint rather than half-opening it.

What it deliberately does not do: change the entitlement. The frozen columns on keyring.workspace_subscription move when Stripe says the subscription exists, through POST /v1/webhooks/stripe, and never on the strength of somebody having opened a payment page.

POST /v1/billing/checkout_session

Answers 201 on success.

Authentication.

  • A krsk_ secret key or a krses_ dashboard session, as Authorization: Bearer.

Body. Validated by this schema, from the control plane's own source:

const CreateCheckoutSession = z.object({
  /**
   * A plan **code**, never a Stripe price id. A caller-supplied price id would
   * let anyone subscribe to a retired price, a price belonging to a plan that
   * is not self-serve, or one created by hand in the Stripe dashboard for a
   * negotiated contract -- none of which the catalog offers. The code is
   * resolved here against the same `where retired_at is null` the published
   * ladder is read through.
   */
  plan_code: z.string().min(1).max(64),
});

Example.

Request
curl https://api.keyringdev.com/v1/billing/checkout_session \
  -X POST \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "plan_code": "team"
  }'
Response
HTTP/1.1 201 Created
Content-Type: application/json

{
  "object": "checkout_session",
  "id": "cs_reference_muqfmpe2",
  "url": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3",
  "plan_code": "team"
}

On this page