The entry surface
Studio
You write the sentence. A model fills in a typed spec, the engine runs that spec over the SEC corpus under three clocks, and the result counts the entries that landed before EDGAR accepted the filing.
Status: building. The engine and both routes are written and
tested, and every number on this page came out of them over the
corpus in our tree. The deployment is not done, so
POST /v1/studio/runs is not a call you can make from
an account today, and runs still live in the process that made
them.
To hear when the routes open, write to operations@aqx.llc.
- Window
- 2022-11-01 .. 2023-03-31
- Sessions
- 114, weekday calendar
- Rows read
- 287,929
- Filings
- 158, all stamped
- Clocks run
- 3, plus one blind arm
- Receipt
- sha256:cee4495e…82828c
{"prompt": "buy Dow companies filing an 8-K and hold five trading days"}
{
"version": 1,
"name": "8-K, Dow window, five sessions",
"trigger": {"form": "8-K", "item_codes": [],
"source": "sec.edgar", "include_amendments": false},
"universe": {"basket": "djia-2023"},
"filters": {"title_contains": [], "start": "", "end": ""},
"entry": {"clock": "acceptance", "session": "next_open",
"offset_minutes": 0},
"hold": {"sessions": 5},
"exit": {"rule": "close_of_last_session"}
}
"param": "entry.clock"
"message": "names the filing's own index date, which at the opening auction
is hours before EDGAR accepted the filing. That instant is not
tradeable and cannot be declared; it runs on every spec as the
leaky arm and appears in the result as the contamination.
Use \"acceptance\"."
Three exchanges with the running engine, 2026-08-27. The refusal is the whole point of the product, so it is on the page rather than in a footnote.
The artifact
What the run gave back
The spec above is the whole vocabulary a strategy has: a trigger, a universe, filters, an entry instant, a hold and an exit. Unknown fields are refused rather than ignored, so a misspelled key stops the run instead of quietly running something else. That spec scanned 287,929 corpus rows between 2022-11-01 and 2023-03-31 and matched 158 8-K filings, every one of them carrying an EDGAR acceptance receipt. This is the part of the result document that answers the question the spec was written to ask.
"headline": {
"clock": "pit",
"total_return": null,
"trades": 0,
"events": 158,
"leak_return_delta": null,
"entries_before_acceptance": 101,
"memory_label": "memory_not_measured",
"memory_note": "No price series covers the mapped population, so this arm has no return to compare."
},
"leak": {
"declared_clock": "pit",
"leaky_clock": "leaky",
"return_delta": null,
"entries_before_acceptance": 101,
"entries_stamped": 158,
"entries_unstamped": 0,
"mean_lookahead_hours": 6.364524,
"max_lookahead_hours": 7.885833,
"sessions_ahead": [{"value": 0, "count": 57}, {"value": 1, "count": 101}]
},
"meta": {
"window_start": "2022-11-01", "window_end": "2023-03-31",
"rows_scanned": 287929, "sessions": 114, "session_calendar": "weekdays",
"prices_available": false, "price_symbols": 0
}
Run on 2026-08-27 with no price cache configured, which is why every
return field reads null instead of 0: an
empty result and a zero return are different answers. The timing
numbers need no prices at all, because they come from the clocks on
the rows. The receipt is elided in the middle for width; the document
carries all sixty-four hex characters, and re-hashing it with the
receipt field blanked is the check
certverify already runs on a certificate.
| Spec | Filings matched | Entered early | Mean lead | Worst |
|---|---|---|---|---|
| 8-K · Dow 30 · hold 5 | 158, all stamped | 101 | 6.36 h | 7.89 h |
| 8-K · SIVB · hold 5 | 10, all stamped | 8 | 7.07 h | 7.88 h |
| 10-K · Dow 30 · hold 20 | 24, all stamped | 20 | 5.60 h | 7.98 h |
The failure it prevents
entry.clock has no default
Asked for “buy when revenue beats,” a model will write you a backtest that acts on the filing date itself. SVB’s receivership 8-K carries an EDGAR acceptance stamp of 22:23:03Z on 2023-03-10, and the opening auction that morning came 7.88 hours earlier. A backtest that enters there is reading a document that does not exist yet, and the equity curve it returns is good for that reason.
So the entry instant is the one field a spec has to state. It takes
acceptance, the EDGAR receipt, or
published, the day-end dissemination bound a nightly
bulk pipeline can act on. Leave it out and the run does not happen:
{
"code": "invalid_request",
"message": "must say when the strategy could act: \"acceptance\" for the EDGAR
acceptance receipt, \"published\" for the day-end dissemination
bound. There is no default. A strategy that does not state its
entry instant gets given midnight on the filing date, which is
the lookahead this run measures.",
"param": "entry.clock"
}
The error object of the standard envelope, wrapped and
indented here to fit the column. param is the JSON path
the caller has to change, so a client can point at the field.
Naming the filed-date instant is refused by name as well.
filed, filed_date,
partition_date, filing_date and
leaky all land on the message at the top of this page. A
negative offset_minutes reaches the same instant by
arithmetic and is refused with its own count of the minutes it would
have travelled back.
The filed-date clock still runs. It runs on every spec, as the leaky
arm, and its entries are what entries_before_acceptance
counts. Refusing it as a declaration is what keeps it a measurement:
the number in the result is the size of a mistake nobody asked the
engine to make.
How it works
Sentence, spec, three clocks, receipt
The sentence becomes a spec
The model is asked for a JSON object matching the schema above, and for nothing else. Its answer goes through the same parser a hand-written spec goes through, so anything that fails to validate comes back as a 400 with the field named. Nothing it returns reaches an interpreter or a shell, which is why it fills in a struct instead of writing a Python file.
The spec runs three times
One basket, one price series, one session calendar, three clocks: the filed-date join, the nightly EDGAR dump bound, and the acceptance receipt. Every other input is held fixed, so the difference between the three arms is attributable to the clock and to nothing else. The absolute return belongs to the basket and the window; the gap between the arms is the part the clock caused.
The identity-blind arm
The engine then runs the same rule a second time over all 35 mapped
issuers, after taking out the issuers the spec chose. A rule that
pays only while those names are in it comes back with a
memory_dependent label and the sentence that produced
it; one that survives their removal comes back as
rule_holds_identity_blind instead. When the arm cannot
be built at all, memory_not_measured is the label and
memory_note carries the reason. That is what the run at
the top of this page reports: no price series covered the basket, so
there were no two returns to compare.
The document hashes to a receipt
Floats are written at six fixed decimals and arrays are sorted by an
explicit key, so the same spec over the same corpus and the same
price cache produces the same bytes every time. The
receipt field holds the sha256 of the result document
with that field blanked, so you can blank it yourself, re-serialise
and compare the digest, which needs the document and nothing from
us.
The universe is a point-in-time table
Tickers resolve against 35 issuers with intervals covering 2022-11
to 2023-03: the 30 Dow members as they stood across the window, plus
GME, GOOGL, META, SIVB and TSLA. A name outside that table is
refused. TWTR is the worked case. Its interval closes on 2022-10-28,
four days before the window opens, so no row lets a 2023 question
resolve it, and resolving it on today’s ticker would be the
lookahead this page is about. A spec that lists no tickers names a
basket instead. The two baskets are the 30 Dow members
(djia-2023) and all 35 mapped issuers
(corpus-mapped).
Integration
Posting a run
Both routes take the same Bearer key or browser session the rest of
the /v1 surface takes. A request body carries either a
spec or a prompt. A body carrying both is refused, because there
would be no way to record which of the two ran.
$ curl -sS -X POST https://api.pit.aqx.llc/v1/studio/runs \
-H "Authorization: Bearer $PIT_KEY" \
-H 'Content-Type: application/json' \
-d '{"spec":{"version":1,"name":"8-K, Dow, five sessions",
"trigger":{"form":"8-K","source":"sec.edgar","include_amendments":false},
"universe":{"basket":"djia-2023"},
"filters":{},
"entry":{"clock":"acceptance","session":"next_open","offset_minutes":0},
"hold":{"sessions":5},
"exit":{"rule":"close_of_last_session"}}}'
The prompt path is the same route with a sentence in place of the spec. The result document carries the spec the model produced, normalised, so you can read what actually ran and post that spec back later without a model in the loop.
$ curl -sS -X POST https://api.pit.aqx.llc/v1/studio/runs \
-H "Authorization: Bearer $PIT_KEY" \
-H "X-Model-Key: $YOUR_MODEL_KEY" \
-H 'Content-Type: application/json' \
-d '{"prompt":"buy Dow companies filing an 8-K and hold five trading days"}'
GET /v1/studio/runs/{id} returns the same document. A run
belongs to the account that made it, and an id from another account
gets the same 404 as an id that was never issued.
Models
The model call
- The default
google/gemini-3.7-flashoverhttps://openrouter.ai/api/v1, both settable per deployment. The prompt is capped at 600 characters and the spec that comes back is under two kilobytes, which is why the call is included rather than metered.- Your own key
- Send it in
X-Model-Keyand it is used for that request instead of ours. The provider is any endpoint speaking OpenAI-shapedPOST {base}/chat/completions, so an OpenRouter, OpenAI or Anthropic credential all work by pointing the base at the right root. - What happens to it
- It is read once per request, handed to the provider adapter, and dropped when the request returns. It is never stored, never logged and never echoed back. The type it is held in renders as
[redacted]through%v,%#v,%qandjson.Marshal, so a log line written by accident prints the placeholder. - What it changes
- Whose provider bill the composition lands on. It does not raise the run budget, because a run costs us the same either way.
- With no key anywhere
- The prompt path answers
model_not_configuredand points at the typed-spec path, which never calls a provider. It does not compose a plausible spec of its own and hand back the run as your idea.
| Plan | Studio runs | API requests |
|---|---|---|
| Researcher | 5 / min | 60 / min |
| Power | 20 / min | 300 / min |
| Desk | 60 / min | 1,200 / min |
A run reads every partition in the window and scores four passes over it, so its budget is counted separately from the 60, 300 or 1,200 requests a minute the same plan buys. The pricing page has both numbers side by side.
Limits
What Studio does not do
- It does not execute model-authored code
- The model produces a spec and the engine reads its fields. Nothing it returns reaches an interpreter, a shell, a query or a file.
- It does not select on 8-K item codes
- The committed rows are EDGAR index metadata and carry no item column. A code like
5.02is accepted, carried onto the spec, and listed infilters_not_appliedwith the reason, because a filter that silently does nothing reports the whole form’s result under the item’s name. - The strategy is deliberately plain
- Long the issuer at the next opening auction, equal weight across whatever is open, a fixed hold, out at the close of the last session. No costs, no slippage, no borrow, no dividends, no taxes, no shorts. Only the difference between the clocks is attributable to the clock.
- Returns are null without a price cache
- The engine reads daily bars from a cache directory and never fetches from a vendor. With none configured it reports the timing half and writes
nullwhere a return would go, and the session list falls back to weekdays withmeta.session_calendarsaying so. - Runs do not survive a restart
- They live in the process that produced them, five hundred at a time. Download the result and keep the receipt. Persisting them on the account database is the next piece of work.
- It does not rank anything
- Comparison belongs to the Certified Leaderboard, which has its own quorum rule and does not list a cell before the quorum is met.
Related
Upstream and downstream
- Point-in-Time API
- The rows a run reads and the acceptance stamps that make the third clock possible.
- Benchmark Datasets
- Where the control arms come from. Studio’s identity-blind arm is the release’s redacted arm applied where a mechanical spec spends issuer identity.
- Evaluation Harness
- The offline runner for an agent rather than a spec, Apache-2.0 and network-off. Exporting a Studio spec into it is not built yet; the spec already round-trips through JSON, which is the input an exporter needs.
- Contamination Certificates
- A signed verdict on one run. Studio’s result already hashes the way a certificate does, with the same blank-and-rehash check.