Simulators
Okta changelog
What changed in each Okta simulator release, newest first.
v0.0.5 — 2026-11-06
- The docs say what the one org holds. The connect section points the SDK’s org URL at the simulator, and the limitations list the users, groups and apps that belong to the org.
v0.0.4 — 2026-11-05
-
Reactivating a user moves
activated, as Okta does. WhenPOST /api/v1/users/{id}/lifecycle/reactivatereturns a RECOVERY user to ACTIVE, the user’sactivatednow reads the reactivation time, alongsidestatusChangedandlastUpdated. Before, it kept the value from when the user was created. -
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. -
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. -
A request with no
Authorizationheader is refused on every route, as Okta refuses it. The twin answers 403E0000005(Invalid session) before it reads the path or body, where its routes answered 403E0000006or, for an id that does not exist, 404E0000007. Any non-emptyAuthorizationvalue is still accepted. -
GET /api/v1/users/meanswers the user who owns the calling token, as Okta does. It answered 404. A credential bound withPOST /_rystic/credentials, or anX-Rystic-Actor, answers that actor; any other answers the scenario’s token owner: a placeholderACTIVEuser the twin now seeds in an otherwise empty org, or Studio Bot, a new Okta Admins integration account, indesign_studio.mealso answers the token owner on/users/me/groups,/users/me/factors,/users/me/factors/catalogand/users/me/factors/{factorId}. Okta’s Terraform provider now gets past its credential check against the twin. -
Every scenario holds Okta’s BUILT_IN Okta Administrators group beside Everyone.
GET /api/v1/groups?filter=type eq "BUILT_IN"lists both, as an Okta org does, in an empty scenario, after a reset and indesign_studio. Okta Administrators lists no members, is in no user’s groups, and refuses profile and membership writes as Everyone does. -
GET /.well-known/okta-organizationanswers the twin’s org metadata, with no credential, as Okta does. It answered 404, which stopped Okta’s SDKs and Terraform provider at their first call. It answers a placeholder org id,pipelineidx, the org’s settings and the org URL on the origin you reached, and ignores anyAuthorizationheader sent with it. -
Your Okta org’s own host reaches the twin under
rystic execand--intercept. The twin claimed onlysubdomain.okta.com, the placeholder in Okta’s spec, so an SDK or the Terraform provider configured withhttps://<org>.okta.comwent to Okta itself. It now claimsokta.comandoktapreview.com, which cover every org on them. An org on a custom domain still points itsorgUrlat the twin. As for every simulated host, a project with no okta twin running with--interceptnow refuses calls to those domains rather than sending them to Okta. -
Assigning an app to Everyone assigns it to every user, links are absolute, and EMEA and government orgs reach the twin.
PUT /api/v1/apps/{appId}/groups/{everyoneId}assigned no one in an org with no stored memberships. Now every user gets aGROUPapp user whose_links.groupnames Everyone, as in Okta, and so does a user you create afterwards,STAGEDincluded. Unassigning Everyone removes those app users, reactivating a user brings them back, and leaving or deleting another group no longer drops an app Everyone still assigns. Every user, factor and app answer now gives its_linkshrefs as absolute URLs on the origin your client reached, where/users/me,POST /usersand a few others gave a bare path. The twin also claimsokta-emea.com,okta-gov.comandokta.milbesideokta.comandoktapreview.com, so a client configured with an EMEA, US government or DoD org URL reaches it underrystic execand--intercept. -
The docs page lists the clients known to work. A new Clients known to work section names each official client checked against this simulator, the release checked, the ways it connects that work and fail, the day it was checked, and any known gap that covers a failure.
-
The Python SDK snippet turns off the HTTPS check in a way okta 3.x honors. The docs passed
"testing": {"disableHttpsCheck": True}toOktaClient, which okta 3.x ignores, so the client refused the twin’shttp://org URL with “Your Okta URL must start with ‘https’”. SetOKTA_TESTING_TESTINGDISABLEHTTPSCHECK=truein the environment instead; the client code keeps only the org URL and token. -
The first calls of the Okta Node.js and Python SDKs and the Terraform provider are now measured against Okta. The calls
@okta/okta-sdk-nodejs8.2.0,okta3.4.7 andokta/okta7.0.0 send first are now among the simulator’s tests, which compare them with real Okta in every full round:GET /api/v1/users?limit=1,GET /api/v1/users/meandGET /api/v1/groups?q=Everyone. The twin’s behavior does not change, and its/users/meanswer does not yet carry Okta’s_links.changePassword, which these tests do not compare. -
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. User lifecycle and passwords, software TOTP factors and bookmark applications 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.
-
Scenario descriptions use customer terms. The Scenarios table and
--list-scenariosdescribe each scenario in American spelling, with no internal references. -
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.
-
Known clients name their registry. The table at the end of Connect your client names each client’s package registry beside it, such as npm, PyPI or GitHub Releases.
-
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 where the simulator differs from Okta and keep only the simulator’s own facts. Sentences are shorter, and a “Differs from Okta” line sits beside the behavior it describes, such as the token checks the simulator skips. Rules with several outcomes, such as which user
GET /api/v1/users/mereturns or what a lifecycle route returns forsendEmail, are tables. The seeding and reset sections keep the registers, ids, clock and what reset clears. They link the shared pages for the rest.
v0.0.3 — 2026-10-12
-
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 admin Noor Haddad adds Hannah Kim to Content and retitles her, suspends the freelancer Ava Chen and removes her from Freelancers, then creates and activates Lena Okafor and adds her to Freelancers, each a call on the twin’s own API under Noor’s bound token. A test that advances the clock past+1hnow sees these changes, and Lena takes the first user ID the twin mints. -
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.2 — 2026-10-11
-
A group lookup’s error ID comes from its own range. An error from
GET /api/v1/groups/{groupId}carries anerrorIdsuch asoae6f3c83c1-0474-49d0-8000-000000000001rather thanoae00000001-0000-4000-8001-000000000001. Ids still count up in their last digits and restart on reset; a test that pinned an olderrorIdneeds the new one. -
The test fixtures seed
transitioningToStatus: null. The fourokta_*fixtures undersim/testdata/scenarios/seeded an empty string for a user with no transition in progress, whichPUT /api/v1/users/{id}andGET /api/v1/groups/{groupId}/usersechoed back. They now seednull, as the twin’s own lifecycle operations write it. -
Offboarding a user removes their app access, as Okta does. Deactivating a user, by
POST /api/v1/users/{id}/lifecycle/deactivateor a firstDELETE, and purging one remove their app assignments, direct and group-derived:GET /api/v1/apps/{appId}/users/{userId}answers 404E0000007and the app’s user list leaves them out. A group assigned to an app afterwards gives a deactivated member no access, adding a deactivated user to a group is refused 403E0000038, and activating the user again restores their group-derived access but not a direct assignment. Each was measured against a live Okta org. -
Deleting a group, an app or a user removes what names it, as Okta does. After
DELETE /api/v1/groups/{groupId},GET /api/v1/apps/{appId}/groups/{groupId}answers 404E0000007, and the app access members held only through that group goes with it. A direct assignment, or access held through another assigned group, stays. Deleting an app removes its user and group assignments, and purging a user removes their enrolled factors. The group behavior was measured against a live Okta org. -
A JSON Patch on an app’s group assignment is applied.
PATCH /api/v1/apps/{appId}/groups/{groupId}with an array such as[{"op":"replace","path":"/priority","value":9}]now changes the assignment: the answer and a laterGETcarry the replacedpriority,profileorprofileattribute, and an empty array changes nothing. Before, the twin answered 200 and left the assignment as it was. -
A user write must carry the profile properties Okta requires.
POST /api/v1/usersandPUT /api/v1/users/{id}refuse a profile that omits, nulls or blankslogin,firstName,lastNameoremailwith 400E0000001and one"<property>: The field cannot be left blank"cause per such property, plus Okta’s"login: Username must be in the form of an email address"when a create has nologin.POST /api/v1/users/{id}may still leave them out, but refuses nulling or blanking one. Before, the twin created a user withoutfirstName,lastNameoremail, replaced one withoutfirstNameorlastName, and let a partial update null either name. -
Every link in
design_studioleads back to the twin. Seeded users’ and groups’_links, on every route that answers them, are on the origin you reached the twin on, as the links the twin builds already were. They named a host on Okta’s own domain, so following one left the twin; a test that resolved a link’s path against its org URL can now follow the link as it is. The fourokta_*test fixtures undersim/testdata/scenarios/moved the same way. -
Listed users no longer carry
realmId.GET /api/v1/usersanswers each user without it, as Okta does and asGET /api/v1/users/{id}already did. Before, a user given arealmIdby a write or a seed showed it in the list and not in its own read. Measured against Okta’s reference org, whose lists and reads carry norealmId. -
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.
v0.0.1 — 2026-10-01
-
A local Okta Management API.
okta-twinserves 49 routes of Okta’s Management API — users and their lifecycle, groups and memberships, bookmark applications with their user and group assignments, password operations and OKTA software-TOTP factors — plus the/_rystic/control plane for reset, seeding and the clock. It accepts any non-emptyAuthorizationheader and never calls Okta, so no activation email is sent and no real directory changes. -
Okta’s user lifecycle, two-step delete included. Users move through
STAGED,ACTIVE,SUSPENDEDandDEPROVISIONEDby the lifecycle routes. A firstDELETEdeactivates a user and a second purges it; a plainGET /api/v1/usersleavesDEPROVISIONEDusers out whilesearchandfilterinclude them; and a profile update on aDEPROVISIONEDuser answers 403E0000038, as an org with deactivated-profile edits turned off does. -
Password operations and software-TOTP factors. Reset, change and expire a password, reactivate a user and reset their factors; list a user’s factor catalog, and enroll, read, activate, verify and delete an OKTA
token:software:totpfactor with codes computed from its shared secret. Other factor types enroll but do not activate, and OAuth/OIDC and browser sign-in are not served. -
One shipped world,
design_studio. Website Design Contractors’ Okta org: 36 users (33ACTIVE, threeDEPROVISIONEDex-staff) in ten groups, Everyone included, each profile carrying title, department and manager. Free boots it andPOST /_rystic/resetreturns to it; Pro and Enterprise boot an empty org unless--scenario design_studionames it. -
Measured against a live Okta org. The 2026-10-01 full-suite round matched 143 of 143 probes against an Okta Integrator Free Plan org.
HOWTO.mdstates what the suite leaves out and the differences this release ships with.