Concepts
Test clocks exist in the sandbox only. A test clock holds a frozen time; companies attached to it see that time instead of the wall clock. Advancing the clock runs one simulated day after the other, in order, and then the clock is ready again.
mutation { createTestClock(name: "Q1 run", frozenTime: "2026-01-02T08:00:00Z") { id status } }
mutation { advanceTestClock(id: "…", to: "2026-04-05T08:00:00Z", options: { submitFilings: true }) { status advanceTarget } }
query { testClocks { id name frozenTime status advanceTarget companies } }
mutation { deleteTestClock(id: "…") }
Attach a company when you create it (createPlatformCompany(testClockId: …)) or seed one (seedTestCompany(input: { template, testClockId }), see Demo companies). A company is on at most one clock.
Scopes: platform_companies:read for testClocks, platform_companies:write for the rest.
What a day runs#
For each day, in this order, for every company on the clock:
- The bank-feed simulator posts that day's transactions through the normal bank-feed ingestion path.
- The scheduled jobs that normally run by wall time run once, at the clock's time: recurring invoices, payment matching, paid status, reminders, payment batches, depreciation, loan schedules, automations, filing policy, tax signals (Mondays), the tax advisor (first of the month), document requests.
- With
submitFilings, drafts that are ready are submitted, and the simulated Digipoort answers. - With
closeFinancialYear, a financial year that has ended is closed.
A step that fails for one company is recorded as a warning (at most 20 are kept) and the day goes on. A failure of the loop itself marks the clock failed with lastError.
Only one clock advances at a time per platform. A second advanceTestClock on a busy platform queues behind the first; on a clock that is already advancing it answers TEST_CLOCK_BUSY.
Simulated Digipoort#
A clocked company's filings go to a simulated Digipoort, not to conformance. A delivery is accepted (status 500) unless the amount due ends in .13, which is rejected (status 510), so you can test both outcomes. The simulator follows the statuses of the Digipoort Happyflow tests; the real status codes and messages of your own company's live filings may differ.
Filing for a seeded company needs the same consent as any other: a company has to grant the tax authorisation before submitFilings submits for it.
Events#
test_clock.ready and test_clock.failed are platform-level events. Both carry testClockTime, the clock's time when the event was written. Events about a clocked company also carry testClockTime, next to the real created. Their JSON schemas are published with the event catalogue.
| Event | Data |
|---|---|
test_clock.ready | the clock, advancedFrom, days, warnings |
test_clock.failed | the clock and error.message |
Errors#
| Code | When |
|---|---|
SANDBOX_ONLY | the call is made outside the sandbox |
TEST_CLOCK_NOT_FOUND | no such clock on your platform (also for another platform's clock) |
TEST_CLOCK_LIMIT | more than 10 clocks on the platform |
TEST_CLOCK_BACKWARDS | to is not after the clock's time |
TEST_CLOCK_RANGE_TOO_LARGE | one advance covers more than 400 days |
TEST_CLOCK_BUSY | the clock is already advancing |
Limits#
- Deleting a clock deletes the companies on it.
- Rows keep their real
createdtimestamp. Only business logic (due dates, periods, reminders, filings) follows the clock. - Some jobs still use wall time. The following are converted: recurring invoices, reminders, payment batches, depreciation, loan schedules, automations, document requests, paid status, payment matching, tax filings, filing policy, tax signals and the tax advisor. Anything else runs on the real date.
- A 365-day run of the logistics template is measured nightly against a 60 minute budget.