A test clock is a sandbox clock that you control. Customers created on a test clock, and their subscriptions, read the clock's frozen_at as the current time instead of real time. Use a test clock to check what your integration does when a trial ends, a renewal charges, or a payment retry runs, without waiting days or weeks for it to happen.
Test clocks exist only in sandboxes. Every test clock route needs a test-mode credential: a live credential gets 403 FORBIDDEN with details[0].reason test_mode_required, whatever scopes it holds. Listing and retrieving clocks requires developer.test_clocks.read or developer.test_clocks.write. Creating and archiving them requires developer.test_clocks.write. Both scopes can be granted to live keys, so a live key can grant them to the sandbox keys it creates, but the routes themselves only accept test credentials.
Create a test clock#
POST /v1/test-clocks creates a clock with status ready, stopped at frozen_at. Both fields are optional.
curl -X POST https://api.withflintpay.com/v1/test-clocks \
-H "Authorization: Bearer flint_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"frozen_at": "2026-11-01T00:00:00Z",
"name": "Annual renewal test"
}'
frozen_at defaults to the time of the request. Fractional seconds are dropped. It can be up to 5 years after the request time, and no more than 1 hour before it, to allow for clock skew on your side. A value outside that range returns 400 INVALID_REQUEST with param: "frozen_at". name is your label, from 1 to 255 characters.
A new clock has version 1 and sends test_clock.created. There's no limit on the number of clocks in a sandbox.
List and retrieve test clocks#
curl "https://api.withflintpay.com/v1/test-clocks?status=ready&status=archived" \
-H "Authorization: Bearer flint_test_YOUR_KEY"
GET /v1/test-clocks returns the sandbox's clocks, newest first, then by test_clock_id descending. Set page_size from 1 to 100, with 20 by default, and pass next_page_token as the next request's page_token. Archived clocks are left out unless the status filter names archived. Repeat status, or send comma-separated values, to match several statuses.
curl https://api.withflintpay.com/v1/test-clocks/tclk_01K8ABCDEF0123456789ABCDEF \
-H "Authorization: Bearer flint_test_YOUR_KEY"
GET /v1/test-clocks/{test_clock_id} returns a clock in this sandbox, including an archived one. A clock in another sandbox returns 404 RESOURCE_NOT_FOUND with param: "test_clock_id", the same as an unknown ID.
Archive a test clock#
curl -X DELETE "https://api.withflintpay.com/v1/test-clocks/tclk_01K8ABCDEF0123456789ABCDEF?expected_version=1" \
-H "Authorization: Bearer flint_test_YOUR_KEY"
DELETE /v1/test-clocks/{test_clock_id} archives the clock and returns it with status archived. The clock stays readable by ID, never advances again, and no new customer can be created on it. Customers and subscriptions already on it don't change: they keep the clock's frozen_at as their current time, so their subscriptions don't bill again. Cancel those subscriptions first if you want them canceled, or reset the sandbox to remove everything.
Status before DELETE | Result |
|---|---|
ready or failed | 200 with status archived. version increases by 1 and Flint sends test_clock.archived. |
archived | 200 with the clock unchanged. No event is sent. |
advancing | 409 ACTION_NOT_ALLOWED with current_status: "advancing" and reason: "in_progress". Wait until the clock is ready or failed. |
expected_version is optional. If it doesn't match the clock's version, the request returns 409 VERSION_CONFLICT with the current version and changes nothing.
The test clock resource#
| Field | Behavior |
|---|---|
test_clock_id | The clock's ID, beginning with tclk_. |
name | Your label. Omitted when you didn't set one. |
status | ready, advancing, failed, or archived. See statuses. |
frozen_at | The time the clock shows. Customers on the clock and their subscriptions treat it as the current time. During an advance it is already the time the clock is moving to. |
created_at, updated_at | When the clock was created and last changed, in real time. |
version | Increases when the clock is created, when an advance starts, when it finishes or fails, and when the clock is archived. Send it as expected_version to require the version you read. |
Statuses#
| Status | Meaning |
|---|---|
ready | Stopped at frozen_at. You can advance it and create customers on it. |
advancing | Flint is moving the clock's customers and their subscriptions to frozen_at, running the billing that falls due along the way, in order. Until the clock is ready, resources on it can show any time between the previous frozen_at and the new one. |
failed | Flint couldn't finish an advance, and resources on the clock may have processed only part of it. The clock can't advance again: archive it and create a new one. Final. |
archived | Archived with DELETE. Readable, but it never advances and takes no new customers. Final. |
status is a closed set. Branch on it to decide whether to wait, continue, or start over with a new clock.
Events#
| Event | When |
|---|---|
test_clock.created | A clock was created. |
test_clock.advancing | An advance started. |
test_clock.ready | An advance finished. Flint records it after every event the advance caused. |
test_clock.failed | An advance failed. |
test_clock.archived | A ready or failed clock was archived. |
Each event carries the clock as data.object. Events go to the sandbox's webhook endpoints, like any other sandbox event.
What follows the clock#
Customers created on a test clock, their subscriptions, and those subscriptions' payment retries use the clock's time wherever Flint decides or records a point in their billing lifecycle. Everything else uses real time.
| Behavior | Follows the clock |
|---|---|
| Trial end, then activation and the first charge | Yes |
subscription.trial_ending and subscription.renewal_upcoming notices | Yes |
Renewals at next_billing_at | Yes |
| Scheduled starts | Yes |
| Payment retries and the action when retries run out | Yes |
| Cancel at period end, and timed pauses that resume on their own | Yes |
Subscription _at fields other than created_at and updated_at | Yes |
created_at and updated_at on every resource | No |
| Webhook envelope times, request logs, and timelines | No |
| Orders, payment intents, invoices, refunds, disputes, and payouts | No |
| Card processing, 3D Secure, and bank debit settlement at Stripe | No |
The needs_attention subscription list filter | No |
Sandbox emails go to real addresses, and webhooks are real sandbox deliveries, so expect them when billing runs on a clock.
Resetting the sandbox removes its test clocks with the rest of its data.
