# Lob changelog

What changed in each Lob simulator release, newest first.

What changed in each Lob simulator release, newest first.

## v0.0.11 — 2026-11-06

1. **The docs gather errors and limits in one section.** It shows Lob's error body, the fixed rate-limit headers a create returns, how the address and postcard lists page, and how an idempotency key replays a postcard.

## v0.0.10 — 2026-11-06

1. **The docs link the shared pages for the live feed and boot files.** The connect section names the `lob_python` library, the scenario limits point to the boot seed file, and the seeding example reads the address back by its id.

## v0.0.9 — 2026-11-05

1. **Setting the clock never stamps records before the timeline events it fired.** When `POST /_rystic/clock {"time": ...}` fires timeline events due at its target, the next request's records stamp after theirs instead of reusing their instant; a set past every event still stamps one millisecond past its target, as before.

2. **A paced clock keeps wall time, and `rystic run --speed 0` stops it.** Under `RYSTIC_PACE`, each tick moves the clock by the wall time that passed since the last, so a tick missed while the twin was busy no longer makes timeline events fire late. `rystic run` hands the twin only the pace it resolved, so `--speed 0`, a scenario without events, `--tape` and `--live` keep the clock manual even when your shell exports `RYSTIC_PACE`.

3. **Indexed list query parameters.** `include` and `size` sent as `include[0]=…&include[1]=…`, as some SDKs encode arrays, read the same as the repeated parameter on `GET /addresses` and `GET /postcards`.

4. **Swagger UI to read and call the twin from the browser.** `/_rystic/swagger`, linked from the state UI header, is Swagger UI over the published API trimmed to the operations this twin serves; Try it out sends each request to the twin.

5. **The twin documents its own control plane in Swagger.** `/_rystic/swagger` now lists a **Rystic control plane** spec, served at `/_rystic/swagger/spec/rystic-control-plane.json`, describing the `/_rystic/*` routes this twin serves (its scenarios, inspect views and seedable registers by name) and its `/_tracking_event` route for recording a postal scan on a postcard.

6. **A state dump seeds back as it was, and the next record gets a fresh id.** `GET /_rystic/inspect` posted to `POST /_rystic/seed` after a reset restores every record where it was, per-caller records included, which used to land under a doubled key. The next record created after any seed passes every uuid and event id the simulator minted that the seed holds. `GET /_rystic/inspect?view=sequences` lists every id counter, and a world document's new `sequences` key restores them, so ids the simulator renders from a counter continue where the dump left off.

7. **A slow client no longer holds up the others.** The simulator reads a request's whole body before it starts serving it and sends the answer after, so a client that uploads slowly, or reads a large answer slowly, no longer delays other clients' requests. Answers are unchanged; one larger than 4 MiB still makes other requests wait while it is sent.

8. **The call log shows what each call asked for and whether it succeeded.** Each `GET /_rystic/calls` entry now carries `args`, the call's query and body arguments with credentials redacted (long values cut, file contents left out), and `ok`, false for an answer that is not 2xx. A path the simulator does not serve is marked `unknown_route: true`. `?tail=N` returns only the last N calls, and `dropped` counts the calls the log no longer keeps.

9. **The docs are one page, with a page per feature.** The archive ships `README.md` in place of `HOWTO.md`, with the same sections in the same order as every Rystic simulator's page, and `CHANGELOG.md` beside it. The Dashboard and the mail stream (tracking scans and the cancellation window) each have their own page under `docs/features/`, and every example on these pages runs as written against a fresh simulator.

10. **The docs page links the admin token section.** The page now sends you to the admin token's section of the shared-simulators page.

11. **The Scenarios table is easier to scan.** Each row says in a line what the scenario is for, and a list below the table says what each scenario holds.

12. **Pro starts with a 14-day trial.** Free has ended, so every simulator is on Pro and Enterprise, and the docs name only those plans.

13. **Limitations is shorter to read.** It opens with the known issues and one sentence counting the rest. The detailed lists follow, and the website folds them behind "Show all limitations".

14. **The page has no Plans section.** The Quickstart names the plans that run the simulator, and the pricing page lists what each plan includes.

15. **The docs read more plainly, mark each difference from Lob and keep to the simulator's own facts.** Sentences are shorter, and each behavior that differs from Lob is marked "Differs from Lob" where it's described, such as how long a postcard stays cancelable, the `Lob-Version` header and how ids are made. Rules with several outcomes, such as what the scan route refuses, are tables, and the known issues use the same plain wording. Auth, seeding and reset keep only what is particular to Lob and link the shared pages for the rest.

## v0.0.8 — 2026-10-12

1. **The scripts in this archive are MIT-licensed.** `check.sh` and the archive's other scripts are published under the MIT License at https://github.com/Rystic-Inc/rystic-scripts, so you can copy, change and share them. `LICENSE.txt` no longer covers them; it still governs the twin itself.

2. **A world can carry its own timeline.** A `--seed` file, or a complete world document posted to `POST /_rystic/seed`, may add `credentials`, binding presented credentials to actors, and `events`: calls the twin makes on its own API at set sim times as one of those actors, through the same handlers and egress as your requests. A top-level `credentials` or `events` key the twin used to ignore now takes effect. `RYSTIC_PACE=<speed>` plays the clock, and the timeline, in real time, and an event's `at` may be `+<duration>`. `/_rystic/inject`, `--inject` and `RYSTIC_INJECT` now need `state_set` instead of `scenarios`.

3. **A timeline call can hand what it minted to later events.** A `call` event may add `bind`, `{"<name>": "<JSON Pointer>"}`, saving fields of its answer, such as an ID the twin minted; a later event uses one as `${name}` in a call's path, headers or body or in a seed event's record keys and values (a register name cannot be bound). A schedule naming a `${name}` no earlier call binds is refused at load, and an event whose binding call failed records an error instead of firing.

4. **`design_studio` carries a timeline.** As the clock moves past `2026-03-01T00:00:00Z`, the world saves Harborview Dental's second office, sends a spring-checkin postcard to Redwood Realty under an idempotency key, schedules one to Atlas Moving and cancels it a day later, then lists the campaign, each a call on the twin's own API under the world's bound test key. A test that advances the clock past `+1h` now sees these records.

5. **A world can move a postcard through the mail.** `POST /_tracking_event` records a USPS scan (`In Transit`, `Delivered`, `Returned to Sender` and Lob's other documented names) on a postcard's `tracking_events`, so a world's timeline can carry a piece from mailing to delivery.

6. **A clock jump fires each timeline event at its own time.** When one `POST /_rystic/clock` or paced tick passes several events, each fires with the clock at its `at`, so the records it creates carry that time, and the clock then lands where the move asked. An event already due when the clock moves fires at the current time.

7. **A clock jump stamps each timeline event at its exact instant.** When a clock move passes timeline events, the records each one makes carry exactly its `at`, to the millisecond, whatever traffic came before it, so one jump leaves the same records as stepping the clock to each event in turn.

8. **A timeline call can carry raw bytes, XML or a JSON array.** A `call` event may send `body_text` or `body_base64` instead of `body`; the twin sends it as written, under the route's own content type (XML, bytes, JSON or a form), so a scheduled call can upload an object's bytes or post an XML document, and `${name}` fills in `body_text`. A `body` may be a JSON array where the route reads one. A call that answers XML binds from its elements by JSON Pointer, such as `/InitiateMultipartUploadResult/UploadId`.

## v0.0.7 — 2026-10-11

1. **`check.sh` fails fast on a port that never answers.** Its port check and boot wait give each `/_rystic/version` probe two seconds, so an address where something accepts connections but never replies fails the check with a message instead of hanging.

2. **The test fixtures seed Lob's render status on postcards.** In `lob_mailroom`, `lob_returned_mail` and `lob_campaign_blast` under `sim/testdata/scenarios/`, a mailed postcard's `status` was a mail state (`delivered`, `in_transit`, `returned_to_sender`, ...) or `canceled`. It is now `rendered`, a value of Lob's PDF render status; the mail's progress stays in `tracking_events`, and the canceled card is still marked `deleted: true`.

3. **Deleting an address marks the postcards sent with it, as Lob does.** After `DELETE /addresses/{adr_id}`, a postcard sent from or to that address is still served, and its embedded `from` or `to` address answers `deleted: true`. Measured against Lob's test environment.

4. **A list's `next_url` and `previous_url` lead back to the twin, and `/v1` paths answer.** Both list cursors name the host your client reached the twin on, under `/v1`, where they named `api.lob.com`, so a client that follows one stays on the twin. Every Lob path now answers with and without the `/v1` prefix, so an SDK's host override may keep Lob's `https://api.lob.com/v1` form with the twin's host. A twin started with `rystic run --intercept` keeps `api.lob.com` in those URLs, which its gateway routes to the twin.

5. **A request without credentials answers Lob's `Missing authentication`.** Every route answers a request that presents no Basic credential, with no `Authorization` header or one under another scheme such as `Bearer`, with Lob's `401` `{"error":{"message":"Missing authentication","status_code":401,"code":"unauthorized"}}` before it reads anything else, so a refused create writes nothing; it answered "Your API key is not valid…" or "The request isn't authorized." before. A Basic header with an empty username, or with a payload that is not base64, still answers the route's own `401`.

6. **A seeded postcard renders as a created one.** In `design_studio` and any world you seed, a postcard's `to` and `from` answer the address objects a created postcard embeds, where they answered bare address IDs, and a record missing `object` (or a postcard missing `carrier`) takes Lob's value. `design_studio`'s postcards also carry `raw_url`, `print_speed`, `to_be_expunged_date`, `qr_code`, `completed_at`, `is_creative_proof`, `is_dashboard`, `usps_campaign_id` and `lob_credits_funding_status`, and its addresses `recipient_moved`, as created ones do.

7. **The guide's Coverage and Limitations are generated from the twin.** Coverage lists every route the twin serves at its `/v1` path, and Limitations end with the twin's open issues and the spec operations, unmeasured branches and unmodeled behaviors it leaves out, regenerated whenever the twin changes. The postcard list's total count is requested as `include[]=total_count`, the form Lob takes.

8. **`lob-assets.com` render URLs are declared Lob's asset host.** A postcard's `url`, `raw_url` and thumbnail URLs on `lob-assets.com`, seeded or answered, are handed out as written and never rebased onto the twin, by declaration (`pipeline_policy.json` `host_roles`) rather than by omission, so a seed carrying Lob's own render URLs passes `RYSTIC_SEED_GATE=reject`. The twin serves no render behind them.

## v0.0.6 — 2026-09-30

1. **Rate-limit headers keep their lowercase spelling.** `ratelimit-limit`, `ratelimit-remaining` and `ratelimit-reset` on address and postcard creation now reach the wire lowercase instead of as `Ratelimit-Limit` and friends, so a client that keys on the received header name reads the names as written.

2. **The state UI shows the account as the Lob dashboard.** On Pro and Enterprise, `/_rystic/ui` opens a live Lob-style view of the twin — postcards with their proof, addresses and tracking, and the address book — where you can send and cancel postcards and add or delete addresses through the API, beside Mail stream and Calls cards.

## v0.0.5 — 2026-09-26

1. **The guide starts from an API key and says what each plan reaches.** `HOWTO.md` now opens with `rystic login rys_…`, says what Free, Pro and Enterprise boot and which control-plane routes each answers, and states that `DELETE /addresses/{adr_id}` is the one route that checks no credential; `SCENARIOS.md` says what each plan boots. The `rystic features` example that filtered on a status no postcard carries now filters `design_studio` by client, the idempotent-send example mails between two of `design_studio`'s own addresses, and the features and commands that need Pro or Enterprise say so. `check.sh` finds the API key `rystic login` stored as well as a license file, looked up in the CLI's order.

## v0.0.4 — 2026-09-26

1. **Only `design_studio` ships as a named scenario.** The older scenarios are now test-only fixtures and are no longer accepted by `--scenario`, `RYSTIC_SCENARIO`, or `POST /_rystic/scenario`. Tests and evaluations can load their unchanged register maps and clocks from `sim/testdata/scenarios/` through the control plane.

## v0.0.3 — 2026-09-25

1. **Self-serve on the metered edition replaces the free edition.** A Rystic account's token runs the metered build: `rystic login rys_…` once, then `rystic run` or `docker run -e RYSTIC_TOKEN=…`. It carries every scenario and control-plane route your plan includes, and bills active 30-second blocks against the plan's allowance. The fixed-world free download and its usage report are gone.

2. **A new world, `design_studio`, now the default.** `--scenario design_studio` boots Website Design Contractors' account: the studio's return address, 38 client addresses and 63 first-class postcards, a holiday campaign and a thank-you card after each site launch, each tagged `metadata[campaign]` and `metadata[client]`, with one holiday card returned to sender. The clients are the same ones the studio's Slack, Stripe and Resend worlds hold. A lease that grants no scenarios boots it instead of `lob_mailroom`.

3. **Every probe matches live Lob.** The last full-suite round scored **21 of 21 probes on 2026-09-24** against real `api.lob.com`, and the four differences the first release listed are repaired: a last page answers `next_url: null`, an inline `to` or `from` carries `email` and `phone` as null and a postcard read answers `deleted: false`, a canceled postcard reads back as the canceled piece with `deleted: true`, and the captured 210 King St address reads back with Lob's ZIP+4. One known difference remains, disclosed under Limitations: a failed postcard can still be canceled inside its send window where Lob answers `422 not_deletable`.

## v0.0.2 — 2026-09-24

1. **Postcard cancellation and response consistency.** Canceled postcards remain retrievable with `deleted: true` and disappear from collection lists; created postcards retain front/back thumbnail links. Scheduled filtering compares send and creation dates, and omitted send dates use the documented default cancellation window. Address creation stores its standardized response, while reads preserve stored address values. Lob API keys resolve through the shared HTTP Basic authentication layer, including regenerated testbooks and shape checks. Collection probes isolate their seeded postcards with a fresh metadata filter and exercise pagination with two records.

2. **Updated coverage guidance.** The Lob guides now describe repaired cancellation, pagination, thumbnail and address responses, clarify HTTP Basic authentication, and distinguish seeded mail stages from unverified asynchronous rendering and failure transitions. The guides also disclose the failed-postcard cancellation mismatch.

## v0.0.1 — 2026-09-24

1. **A local Lob for your mail-sending code's tests.** The first release of the
   Lob twin: 8 routes over addresses (create, retrieve, delete, list) and
   postcards (create, retrieve, cancel, list), served from a model validated
   against real `api.lob.com` with a test key. Point your Lob SDK's host at the
   twin — `http://localhost:8080`, no `/v1` — and your existing calls run
   against it unchanged, with form-encoded or JSON bodies.

2. **A retried send is the same postcard.** `POST /postcards` honors an
   `Idempotency-Key` header, or an `idempotency_key` query parameter, as Lob
   does: a replay under the same key answers the original postcard instead of
   sending a second one, so the retry loop around your send is testable without
   mailing anything twice.

3. **Four named worlds, baked in.** `--scenario lob_mailroom` boots five
   postcards standing at every healthy stage from rendered through delivered,
   with their tracking histories; `lob_returned_mail` the returned, failed,
   re-routed and canceled pieces with their `failure_reason`; `lob_campaign_blast`
   six sends under one `campaign_id` for paging and filters; `lob_quiet_desk` one
   of everything. `SCENARIOS.md` says what each is for.

4. **Seed the mailroom your test needs, and get the same one twice.**
   `POST /_rystic/seed` lands addresses, postcards and idempotency keys directly;
   `GET /_rystic/registers` lists every field it accepts. `POST /_rystic/reset`
   rewinds the ID sequence and the clock, so two identical runs give
   byte-identical postcards down to `id`, `send_date` and
   `expected_delivery_date`.

5. **It never reaches the network, and it needs no key.** Any key is accepted
   as the Basic username and the twin dials nothing: nothing you send renders a
   PDF, counts against a rate limit, or could ever print or mail a piece. Not in
   this release: letters, checks, self-mailers, templates, campaigns as a
   resource, address verification and webhooks; a postcard does not move
   through the mail stream on its own — boot a world or seed the stage you
   need.

6. **What is measured, and what is not.** Fidelity is scored differentially
   against real `api.lob.com` with a test key, per response field, not by
   eyeball: the last full-suite round was **13 of 21 probes on 2026-09-24**. That
   is an early number and this release ships it as-is. The misses are four known
   differences, listed in `HOWTO.md` under Limitations: `next_url` is a link on
   the last page where Lob sends null; an inline `to` or `from` address omits
   `email` and `phone`, and a postcard read answers `deleted: false`, where Lob
   sends null; a canceled postcard reads back as 404 where Lob answers the
   canceled piece; and an address reads back with the ZIP you sent, not Lob's
   ZIP+4. Rounds are re-run daily and each release states its own number.

---

Source: https://www.rystic.ai/docs/simulators/lob/changelog · Markdown: https://www.rystic.ai/docs/simulators/lob/changelog.md
