Class CorporateActionsClient
- Namespace
- DatabentoDotNet.Reference
- Assembly
- DatabentoDotNet.Reference.dll
The corporate_actions.* endpoints: what happened to a security, and the documentation the
server keeps about its own events and enumerations.
public sealed class CorporateActionsClient
- Inheritance
-
CorporateActionsClient
- Inherited Members
Examples
// The one endpoint here that bills, and the one that streams: zstd-framed JSON lines, read a row
// at a time.
await foreach (CorporateAction action in client.CorporateActions.GetRangeAsync(
new CorporateActionsGetRangeParams
{
Symbols = Symbols.From("AAPL"),
DateTimeRange = ReferenceDateTimeRange.Between(
Instant.FromUtc(2024, 1, 1, 0, 0), Instant.FromUtc(2025, 1, 1, 0, 0)),
Events = [Event.Div, Event.Fsplt],
}))
{
Console.WriteLine($"{action.EventDate} {action.Event} {action.EventAction} {action.SecurityDescription}");
}
// The two documentation endpoints: bare GETs that return one whole JSON object, and — near
// certainly — the only free endpoints in this namespace.
IReadOnlyDictionary<string, EventDoc> events = await client.CorporateActions.ListEventsAsync();
Console.WriteLine(events["DIV"].Name); // keyed by the uppercase wire code
Remarks
Reached through CorporateActions rather than constructed. Port of
upstream's CorporateActionsClient (corporate.rs:27-96), which holds a mutable
borrow of the outer client; this holds a reference, there being no borrow checker to satisfy.
Three endpoints in two shapes, which is why they shipped in two issues.
GetRangeAsync(CorporateActionsGetRangeParams, CancellationToken) is a POST with a form body that answers with zstd-framed JSON
lines, read a row at a time (#55). ListEventsAsync(CancellationToken) and
ListEnumsAsync(CancellationToken) are bare GETs — no body, no query string — that answer with
one plain JSON object, read whole through ReadJsonAsync<T>(HttpResponseMessage, JsonTypeInfo<T>, CancellationToken) (#56).
Different method, different encoding, different reader, and only one type family in common.
Only one of the three costs money. GetRangeAsync(CorporateActionsGetRangeParams, CancellationToken) returns data and bills for it; the other two return documentation and are, near-certainly, the only free endpoints in this namespace — this repository has already called both against the live API and vendored the responses (#58). "Near-certainly" is a prior rather than a measurement; #57 owns pricing them properly.
The two documentation endpoints return the whole document, and neither streams. A JSON
object is not a sequence of rows: there is no point at which half of one is usable, so an
IAsyncEnumerable<T> would buy nothing and cost the caller a
ToDictionaryAsync. Upstream buffers all three (corporate.rs:57-63,
:75-79, :91-95); for these two that is not a borrow-checker artefact, and for
GetRangeAsync(CorporateActionsGetRangeParams, CancellationToken) it is — see #52.
Fields
RequestCompression
The compression GetRangeAsync(CorporateActionsGetRangeParams, CancellationToken) asks for. Not caller-settable.
public const string RequestCompression = "zstd"
Field Value
Remarks
Upstream hard-codes this (corporate.rs:42) because the response handler requires the
frame; ToFormParameters() renders it and documents
why it is a constant rather than a property. Public and named so a test can assert the value
on the wire against the value the library believes it sends, rather than against a string
typed twice — the same reason RequestCompression and
RequestCompression are.
Methods
GetRangeAsync(CorporateActionsGetRangeParams, CancellationToken)
Streams every corporate action matching parameters over the requested
range, a row at a time as they decompress.
public IAsyncEnumerable<CorporateAction> GetRangeAsync(CorporateActionsGetRangeParams parameters, CancellationToken cancellationToken = default)
Parameters
parametersCorporateActionsGetRangeParamsWhich symbols, over what range of which date, narrowed how.
cancellationTokenCancellationTokenCancels the request and the enumeration.
Returns
- IAsyncEnumerable<CorporateAction>
One row per corporate action, in the order the server sent them.
Remarks
Port of upstream's get_range (corporate.rs:33-65) — with one deliberate
behavioural difference, below.
This costs money, and it is the only endpoint in this class that does. Reference data
is a separate Databento product from historical market data: an API key entitled for one is
not necessarily entitled for the other, and a 403 here on an otherwise working key
means exactly that rather than a broken credential. Unlike a batch job, a stream can be
stopped part-way — break out of the await foreach and the response is disposed
on the way out, which is half of why this streams. It also defaults to allocating ISINs; see
AllocateIsins.
Rows arrive in the server's order, and this method does not sort them. Upstream
buffers the whole response into a Vec and then sorts it by whichever date
Index names (corporate.rs:59-63) — it
can, because it has already paid for the buffer. A stream has not: sorting is what buffering
is, so an IAsyncEnumerable<T> that sorted would be a list wearing a
stream's type. The index is still sent, because it is also what the server filters
on — dropping the sort does not drop the parameter. A caller who needs upstream's order can
have it in one line over the materialised sequence, and pays for the buffer where they can
see it. See ROADMAP.md §6, #52, and
ReadZstdJsonLinesStreamAsync<T>(HttpResponseMessage, JsonTypeInfo<T>, CancellationToken) where the argument is made in
full.
Whether that is observable is still unmeasured, and #57 is where it stops being. If
the server already returns rows in the index's order, dropping the sort changes nothing a
caller can see; if it does not, a caller who needs that order must sort for themselves and
this paragraph has to say so. RealReferenceRequestTests.
CorporateActionsGetRange_ArrivesInTheOrderTheIndexNames asks the server under both
EventDate and TsRecord
— two indexes, because "the server sorts" and "storage order happens to match one index" are
different claims and only the first survives changing it. The mock cannot answer this: it
returns the lines it was given. On 2026-08-29 the account that experiment ran under was
answered 403 license_reference_dataset_no_subscription, so it is written, gated and
waiting on an entitled key rather than on anyone's attention.
Nothing is sent until the enumeration starts. Calling this method builds a query; the
request goes out on the first MoveNextAsync. A caller who never enumerates never
bills. The argument checks below run at the call rather than at that first step, so a
mistake in them faults where it was made.
Exceptions
- ArgumentNullException
parametersis null.- InvalidOperationException
parametersleaves Symbols or DateTimeRange at its type's default value.- DatabentoApiException
The API answered with a non-success status.
ListEnumsAsync(CancellationToken)
Reads every enumeration the corporate actions data uses, keyed by enum group name.
public Task<IReadOnlyDictionary<string, IReadOnlyList<EventEnumVariant>>> ListEnumsAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenCancels the request and the read.
Returns
- Task<IReadOnlyDictionary<string, IReadOnlyList<EventEnumVariant>>>
The variants of each enumeration, keyed by the group name the server uses.
Remarks
Port of upstream's list_enums (corporate.rs:82-96).
This is the dictionary the ten open code types are transcribed from, and the reason
they are open at all: probing it found upstream's own tables behind the server on
SecurityType and Frequency, and stale in both directions on
Event. A group name maps to a type here — SECTYPE to
SecurityType, MANDVOLU to MandVolu — but that mapping
lives in the tables rather than in this method, which returns the server's own key. See
ROADMAP.md §6 and tests/DatabentoDotNet.Reference.Tests/Data/README.md.
A group may list a blank code, and 148 of the 235 the server returned do. Those arrive as a null Code, which is the evidence behind every code carrier reading a blank as "no value" rather than rejecting it.
Exceptions
- ObjectDisposedException
The client has been disposed.
- DatabentoApiException
The API answered with a non-success status.
- JsonException
The body was not a readable document.
ListEventsAsync(CancellationToken)
Reads the server's documentation for every corporate action event it supports, keyed by event code.
public Task<IReadOnlyDictionary<string, EventDoc>> ListEventsAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenCancels the request and the read.
Returns
- Task<IReadOnlyDictionary<string, EventDoc>>
One document per event, keyed by the event code the server filed it under.
Remarks
Port of upstream's list_events (corporate.rs:67-80).
Keyed by the string the server filed each document under, never by a parsed
Event. That is upstream's choice (HashMap<String, EventDoc>)
and it is the right one: an event code this library has never seen still arrives under its
own key, where a caller can find it. Parsing the key would either lose such an entry or
collapse several onto one default. The key is ordinal and
case-sensitive, as upstream's is — AGM is not agm.
What it is for beyond being an endpoint: each document's
Fields says which of CorporateAction's three open maps every
field lands in, so this is the authority for what may legally appear in them (#55). It is
also the only authority for EventCategory, EventLevel and
FieldGroup, none of which list_enums reports a group for.
Exceptions
- ObjectDisposedException
The client has been disposed.
- DatabentoApiException
The API answered with a non-success status.
- JsonException
The body was not a readable document.