Table of Contents

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

string

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

parameters CorporateActionsGetRangeParams

Which symbols, over what range of which date, narrowed how.

cancellationToken CancellationToken

Cancels 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

parameters is null.

InvalidOperationException

parameters leaves 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

cancellationToken CancellationToken

Cancels 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

cancellationToken CancellationToken

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