flap fails on a schedule you choose, so you can test how a client behaves when a dependency goes away. Every request carries its own schedule in the query string; the server keeps no state, so every instance gives the same answer for the same parameters and the same instant. This page is generated from the same tables the server validates with.
Failures are periodic. A period contains outages; during an outage the failure endpoints fail, and outside one they answer 200 ok. Add /inspect to a URL to see what the schedule looks like without triggering anything.
All failure endpoints and /inspect take these query parameters. Every one is optional. Unknown, repeated and empty parameters are errors, so a typo never falls back to a default.
| Parameter | Kind | Accepted values | Default | Bounds |
|---|---|---|---|---|
period | duration | one or more <integer><unit> terms with units ms, s, m, h, d, w (for example 1h30m), or a bare integer meaning seconds | 1d | 1s to 366d |
outage | duration | same duration syntax as period | 15m | 1s to period |
count | integer | non-negative integer, the number of outages per period | derived from availability | 0 to 10000, and at most floor(period / outage) |
availability | decimal percent | decimal percent with up to 4 decimals, for example 99.9 | 90 when count is absent | 0 to 100 |
seed | unsigned 64-bit integer | unsigned 64-bit integer | 0 | 0 to 18446744073709551615 |
placement | enumeration | even, scattered or grid | even | enumerated; grid needs a period divisible by outage |
Give either count or availability, not both: giving both is a 400. With availability the number of outages is derived from it; with count availability follows from the windows. The defaults are a 1 day period, a 15 minute outage, availability 90, even placement and seed 0, which give 10 outages per period and 89.5833 % availability actually achieved.
The placement parameter decides where the outages sit in a period.
even: Outages are spread at equal spacing across the period.scattered: Outage positions are drawn from the seeded stream, so the same seed always gives the same layout. Outages never overlap.grid: The period is cut into slots one outage long and outages occupy distinct slots, drawn from the seeded stream. It needs a period divisible by the outage length and a count of at most the number of slots.Three endpoints fail during an outage, each in a different way. All three accept any HTTP method.
| Kind | Path | During an outage | Parameters | Outside an outage |
|---|---|---|---|---|
status | /status/{code} | Answers the status code from the path (200 to 599) with the text for that code. | schedule parameters and retry_after | 200 ok |
hang | /hang | Holds the response, then answers 200 ok. The hold ends when the outage ends, when duration is up or at the hang cap, whichever comes first, or when the client gives up. Past the concurrent-hang cap it answers 429 at once with an X-Flap-Reason header. | schedule parameters and duration | 200 ok at once |
drop | /drop | Sends 200 headers declaring one byte more than it sends, writes bytes body bytes, then resets the TCP connection. | schedule parameters and bytes | 200 ok |
These parameters belong to one endpoint, in addition to the schedule parameters.
| Parameter | Endpoint | Accepted values | Default | Bounds |
|---|---|---|---|---|
retry_after | /status/{code} | a whole number of seconds, or "remaining" for the time left in the current outage | no Retry-After header | digits only, 0 to 2147483647 |
duration | /hang | same duration syntax as the schedule parameters | hold until the outage ends | 1ms to 366d, and never longer than the hang cap |
bytes | /drop | non-negative integer, the body bytes sent before the reset | 0 | digits only, 0 to the drop-bytes cap |
format | /inspect | json, html or svg; forces the response format regardless of Accept | chosen from Accept | enumerated |
at | /inspect | an RFC 3339 instant or Unix seconds; evaluates the schedule at that instant instead of now | now | years 1 to 8999 |
retry_after adds a Retry-After header to /status failures. The header is always a number of seconds. Give a whole number to send it as is, or remaining to send the time left in the current outage, rounded up to whole seconds and never less than 1. Outside an outage no header is sent. When the outage never ends (availability 0) there is no end to point to, so remaining sends no header.
200 with the body ok.Cache-Control: no-store, so no cache or proxy can replay a failure.X-Flap-State: up or down. A parameter error never consults it, so that header is absent on a 400.{"error": "..."} when Accept prefers application/json. It is checked before the clock is read and never reaches the failure path, so a bad request cannot fail by accident./hang answers 429 with an X-Flap-Reason header when too many hangs are already held.Each instance protects itself with these limits. They are deployment settings, shown here with the values this instance runs.
| Limit | Environment variable | Value | Meaning |
|---|---|---|---|
| Hang cap | FLAP_MAX_HANG | 5m | The longest /hang holds a response, whatever the outage or duration says. Longer values are silently cut to the cap. |
| Concurrent hangs | FLAP_MAX_HANGS | 256 | How many /hang responses may be held at once per instance. Beyond that, /hang answers 429 with X-Flap-Reason: too many concurrent hangs. |
| Drop bytes | FLAP_MAX_DROP_BYTES | 65536 | The most body bytes /drop writes before the reset. A larger bytes value is a 400. |
/inspect never fails. It takes the schedule parameters and describes the windows that overlap the current period, the state now, the next change and ready-to-call links for the three failure endpoints.
format=json|html|svg forces the format. Without it, an Accept header that ranks text/html above application/json (a browser) gets HTML and anything else, including */* (curl), gets JSON.at evaluates another instant, as an RFC 3339 time or Unix seconds. It works on /inspect only.FLAP_BASE_URL when set, else X-Forwarded-Proto and X-Forwarded-Host, else the request host.The JSON document is stable. Fields:
| Field | Type | Meaning |
|---|---|---|
now | string | The evaluated instant: the server clock, or the at parameter. RFC 3339, UTC, millisecond precision, like every instant in the document. |
schedule.period | string | Resolved period in the duration syntax, so it can be fed back as a parameter. |
schedule.outage | string | Resolved outage length in the same syntax. |
schedule.count | number | Outages per period, as given or derived from availability. |
schedule.availability_target | number or null | The requested availability in percent; null when the schedule was given by count. |
schedule.availability_actual | number | The availability the windows give, in percent, rounded down to four decimals. |
schedule.seed | number | The seed. It is an unsigned 64-bit integer, so a JavaScript consumer should read the body as text: numbers above 2^53 lose precision in a double. |
schedule.placement | string | The placement. |
period.index | number | Number of the period containing now, counted from the Unix epoch. |
period.start | string | Start of that period. |
period.end | string | End of that period. |
state | string | up or down at now. |
next_change | string or null | The next instant the state flips; null when it never does (no outages, or outages covering the whole period). |
windows[].start | string | Start of an outage window overlapping the period, ordered by start. |
windows[].end | string | End of the window. |
windows[].start_offset_ms | number | Window start minus period start, in milliseconds. Negative for the tail of a window that began in the previous period. |
windows[].wraps | boolean | True for a window that crosses a period boundary. It is listed in both periods it touches: as its head with a positive offset and as its tail with a negative one. |
links.status | string | Ready-to-call /status link carrying the same schedule parameters. |
links.hang | string | Ready-to-call /hang link carrying the same schedule parameters. |
links.drop | string | Ready-to-call /drop link carrying the same schedule parameters. |
/drop ends the connection with a reset. Through a reverse proxy or ingress, the proxy sees a body that stops short of its declared length after the headers have gone out, and then closes the client connection itself, so the client sees a truncated body or an early close rather than a reset. To test a real reset, connect to the pod directly.Every example below is run against a test server in the project's test suite, so each behaves as described.
A 503 for as long as the outage lasts. availability=0 puts the whole period in outage, so the example always fails.
curl -i 'https://flap.sarak.sh/status/503?availability=0'
Answers 503 with the header X-Flap-State: down.
A 429 with a fixed Retry-After of 120 seconds.
curl -i 'https://flap.sarak.sh/status/429?availability=0&retry_after=120'
Answers 429 with the header Retry-After: 120 and the header X-Flap-State: down.
Retry-After set to the time left in the outage. This one uses the default schedule, so it is a plain 200 whenever no outage is running; a client that obeys the header comes back right as the outage ends.
curl -i 'https://flap.sarak.sh/status/503?retry_after=remaining'
Answers 503 with a Retry-After header and the header X-Flap-State: down.
Any method fails the same way.
curl -i -X POST 'https://flap.sarak.sh/status/500?availability=0'
Answers 500 with the header X-Flap-State: down.
count=0 means no outages at all, so the endpoint answers 200 ok with the state header.
curl -i 'https://flap.sarak.sh/status/503?count=0'
Answers 200 with the header Cache-Control: no-store, the header X-Flap-State: up and a body containing "ok".
Hold the response for 200 ms, then answer 200. Without duration the hold lasts until the outage ends.
curl -i --max-time 30 'https://flap.sarak.sh/hang?availability=0&duration=200ms'
Answers 200 with the header X-Flap-State: down and a body containing "ok". The response is held back first.
Send the headers and 10 body bytes, then reset the connection.
curl -i --max-time 30 'https://flap.sarak.sh/drop?availability=0&bytes=10'
Answers 200 with the header Content-Length: 11 and the header X-Flap-State: down. The connection is then reset before the declared body is complete, so curl reports an error.
Ask what the schedule looks like without triggering a failure. /inspect never fails; it describes the state.
curl -s 'https://flap.sarak.sh/inspect?availability=0'
Answers 200 with a body containing "\"state\": \"down\"".
Evaluate the schedule at another instant with at (RFC 3339 or Unix seconds), here with a schedule that has no outages.
curl -s 'https://flap.sarak.sh/inspect?count=0&at=2030-01-01T00:00:00Z'
Answers 200 with a body containing "\"state\": \"up\"".
Spread two 15-minute outages over an hour on a grid, seeded so the layout repeats.
curl -s 'https://flap.sarak.sh/inspect?period=1h&outage=15m&count=2&placement=grid&seed=7'
Answers 200 with a body containing "\"placement\": \"grid\"".
A misspelled parameter is an error, never a silent default. The state header is absent because the schedule was never consulted.
curl -i 'https://flap.sarak.sh/status/503?avialability=90'
Answers 400 with a body containing "unknown parameter". No X-Flap-State header is sent.
Ask for JSON errors with the Accept header.
curl -i -H 'Accept: application/json' 'https://flap.sarak.sh/status/99'
Answers 400 with the header Content-Type: application/json and a body containing "{\"error\":".