Simulators
Lob changelog
What changed in each Lob simulator release, newest first.
v0.0.11 — 2026-11-06
- 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
- The docs link the shared pages for the live feed and boot files. The connect section names the
lob_pythonlibrary, 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
-
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. -
A paced clock keeps wall time, and
rystic run --speed 0stops it. UnderRYSTIC_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 runhands the twin only the pace it resolved, so--speed 0, a scenario without events,--tapeand--livekeep the clock manual even when your shell exportsRYSTIC_PACE. -
Indexed list query parameters.
includeandsizesent asinclude[0]=…&include[1]=…, as some SDKs encode arrays, read the same as the repeated parameter onGET /addressesandGET /postcards. -
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. -
The twin documents its own control plane in Swagger.
/_rystic/swaggernow 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_eventroute for recording a postal scan on a postcard. -
A state dump seeds back as it was, and the next record gets a fresh id.
GET /_rystic/inspectposted toPOST /_rystic/seedafter 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=sequenceslists every id counter, and a world document’s newsequenceskey restores them, so ids the simulator renders from a counter continue where the dump left off. -
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.
-
The call log shows what each call asked for and whether it succeeded. Each
GET /_rystic/callsentry now carriesargs, the call’s query and body arguments with credentials redacted (long values cut, file contents left out), andok, false for an answer that is not 2xx. A path the simulator does not serve is markedunknown_route: true.?tail=Nreturns only the last N calls, anddroppedcounts the calls the log no longer keeps. -
The docs are one page, with a page per feature. The archive ships
README.mdin place ofHOWTO.md, with the same sections in the same order as every Rystic simulator’s page, andCHANGELOG.mdbeside it. The Dashboard and the mail stream (tracking scans and the cancellation window) each have their own page underdocs/features/, and every example on these pages runs as written against a fresh simulator. -
The docs page links the admin token section. The page now sends you to the admin token’s section of the shared-simulators page.
-
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.
-
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.
-
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”.
-
The page has no Plans section. The Quickstart names the plans that run the simulator, and the pricing page lists what each plan includes.
-
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-Versionheader 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
-
The scripts in this archive are MIT-licensed.
check.shand 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.txtno longer covers them; it still governs the twin itself. -
A world can carry its own timeline. A
--seedfile, or a complete world document posted toPOST /_rystic/seed, may addcredentials, binding presented credentials to actors, andevents: 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-levelcredentialsoreventskey the twin used to ignore now takes effect.RYSTIC_PACE=<speed>plays the clock, and the timeline, in real time, and an event’satmay be+<duration>./_rystic/inject,--injectandRYSTIC_INJECTnow needstate_setinstead ofscenarios. -
A timeline call can hand what it minted to later events. A
callevent may addbind,{"<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. -
design_studiocarries a timeline. As the clock moves past2026-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+1hnow sees these records. -
A world can move a postcard through the mail.
POST /_tracking_eventrecords a USPS scan (In Transit,Delivered,Returned to Senderand Lob’s other documented names) on a postcard’stracking_events, so a world’s timeline can carry a piece from mailing to delivery. -
A clock jump fires each timeline event at its own time. When one
POST /_rystic/clockor paced tick passes several events, each fires with the clock at itsat, 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. -
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. -
A timeline call can carry raw bytes, XML or a JSON array. A
callevent may sendbody_textorbody_base64instead ofbody; 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 inbody_text. Abodymay 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
-
check.shfails fast on a port that never answers. Its port check and boot wait give each/_rystic/versionprobe two seconds, so an address where something accepts connections but never replies fails the check with a message instead of hanging. -
The test fixtures seed Lob’s render status on postcards. In
lob_mailroom,lob_returned_mailandlob_campaign_blastundersim/testdata/scenarios/, a mailed postcard’sstatuswas a mail state (delivered,in_transit,returned_to_sender, …) orcanceled. It is nowrendered, a value of Lob’s PDF render status; the mail’s progress stays intracking_events, and the canceled card is still markeddeleted: true. -
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 embeddedfromortoaddress answersdeleted: true. Measured against Lob’s test environment. -
A list’s
next_urlandprevious_urllead back to the twin, and/v1paths answer. Both list cursors name the host your client reached the twin on, under/v1, where they namedapi.lob.com, so a client that follows one stays on the twin. Every Lob path now answers with and without the/v1prefix, so an SDK’s host override may keep Lob’shttps://api.lob.com/v1form with the twin’s host. A twin started withrystic run --interceptkeepsapi.lob.comin those URLs, which its gateway routes to the twin. -
A request without credentials answers Lob’s
Missing authentication. Every route answers a request that presents no Basic credential, with noAuthorizationheader or one under another scheme such asBearer, with Lob’s401{"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 own401. -
A seeded postcard renders as a created one. In
design_studioand any world you seed, a postcard’stoandfromanswer the address objects a created postcard embeds, where they answered bare address IDs, and a record missingobject(or a postcard missingcarrier) takes Lob’s value.design_studio’s postcards also carryraw_url,print_speed,to_be_expunged_date,qr_code,completed_at,is_creative_proof,is_dashboard,usps_campaign_idandlob_credits_funding_status, and its addressesrecipient_moved, as created ones do. -
The guide’s Coverage and Limitations are generated from the twin. Coverage lists every route the twin serves at its
/v1path, 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 asinclude[]=total_count, the form Lob takes. -
lob-assets.comrender URLs are declared Lob’s asset host. A postcard’surl,raw_urland thumbnail URLs onlob-assets.com, seeded or answered, are handed out as written and never rebased onto the twin, by declaration (pipeline_policy.jsonhost_roles) rather than by omission, so a seed carrying Lob’s own render URLs passesRYSTIC_SEED_GATE=reject. The twin serves no render behind them.
v0.0.6 — 2026-09-30
-
Rate-limit headers keep their lowercase spelling.
ratelimit-limit,ratelimit-remainingandratelimit-reseton address and postcard creation now reach the wire lowercase instead of asRatelimit-Limitand friends, so a client that keys on the received header name reads the names as written. -
The state UI shows the account as the Lob dashboard. On Pro and Enterprise,
/_rystic/uiopens 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
- The guide starts from an API key and says what each plan reaches.
HOWTO.mdnow opens withrystic login rys_…, says what Free, Pro and Enterprise boot and which control-plane routes each answers, and states thatDELETE /addresses/{adr_id}is the one route that checks no credential;SCENARIOS.mdsays what each plan boots. Therystic featuresexample that filtered on a status no postcard carries now filtersdesign_studioby client, the idempotent-send example mails between two ofdesign_studio’s own addresses, and the features and commands that need Pro or Enterprise say so.check.shfinds the API keyrystic loginstored as well as a license file, looked up in the CLI’s order.
v0.0.4 — 2026-09-26
- Only
design_studioships as a named scenario. The older scenarios are now test-only fixtures and are no longer accepted by--scenario,RYSTIC_SCENARIO, orPOST /_rystic/scenario. Tests and evaluations can load their unchanged register maps and clocks fromsim/testdata/scenarios/through the control plane.
v0.0.3 — 2026-09-25
-
Self-serve on the metered edition replaces the free edition. A Rystic account’s token runs the metered build:
rystic login rys_…once, thenrystic runordocker 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. -
A new world,
design_studio, now the default.--scenario design_studioboots 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 taggedmetadata[campaign]andmetadata[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 oflob_mailroom. -
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 answersnext_url: null, an inlinetoorfromcarriesemailandphoneas null and a postcard read answersdeleted: false, a canceled postcard reads back as the canceled piece withdeleted: 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 answers422 not_deletable.
v0.0.2 — 2026-09-24
-
Postcard cancellation and response consistency. Canceled postcards remain retrievable with
deleted: trueand 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. -
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
-
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.comwith 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. -
A retried send is the same postcard.
POST /postcardshonors anIdempotency-Keyheader, or anidempotency_keyquery 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. -
Four named worlds, baked in.
--scenario lob_mailroomboots five postcards standing at every healthy stage from rendered through delivered, with their tracking histories;lob_returned_mailthe returned, failed, re-routed and canceled pieces with theirfailure_reason;lob_campaign_blastsix sends under onecampaign_idfor paging and filters;lob_quiet_deskone of everything.SCENARIOS.mdsays what each is for. -
Seed the mailroom your test needs, and get the same one twice.
POST /_rystic/seedlands addresses, postcards and idempotency keys directly;GET /_rystic/registerslists every field it accepts.POST /_rystic/resetrewinds the ID sequence and the clock, so two identical runs give byte-identical postcards down toid,send_dateandexpected_delivery_date. -
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.
-
What is measured, and what is not. Fidelity is scored differentially against real
api.lob.comwith 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 inHOWTO.mdunder Limitations:next_urlis a link on the last page where Lob sends null; an inlinetoorfromaddress omitsemailandphone, and a postcard read answersdeleted: 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.