Skip to content

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.

Read the spec format

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

POST /v1/studio/runs what the customer typed

{"prompt": "buy Dow companies filing an 8-K and hold five trading days"}

the only thing the model is allowed to return 201 · spec, normalised

{
  "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"}
}

the same run with "clock": "filed_date" 400 · invalid_request

"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.

101 / 158 entries the filed-date join would have placed before EDGAR accepted the filing
6.36h mean lead of those entries, 7.89h at the worst single filing
57 of the 158 land on the same session under both clocks, so the gap is not uniform

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.

result.json · headline, leak and meta receipt sha256:cee4495e…82828c

"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.

Three specs run over the same corpus on 2026-08-27, same window, no prices.
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:

POST /v1/studio/runs · entry.clock omitted 400 · error object

{
  "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).

How the identity table is built

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.

POST /v1/studio/runs 201 · one result in the envelope

$ 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.

POST /v1/studio/runs · prompt, with your own model key 201

$ 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-flash over https://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-Key and it is used for that request instead of ours. The provider is any endpoint speaking OpenAI-shaped POST {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, %q and json.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_configured and 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.
Run budget, metered apart from the read routes.
Plan Studio runs API requests
Researcher5 / min60 / min
Power20 / min300 / min
Desk60 / min1,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.02 is accepted, carried onto the spec, and listed in filters_not_applied with 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 null where a return would go, and the session list falls back to weekdays with meta.session_calendar saying 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.