Table of Contents

Class MassiveHttpTransport

Namespace
MassiveDotNet.Http
Assembly
MassiveDotNet.dll

Issues authenticated requests against the Massive platform API and deserializes responses using source-generated metadata, or copies a document body to a caller's stream.

public sealed class MassiveHttpTransport : IDisposable
Inheritance
MassiveHttpTransport
Implements
Inherited Members

Remarks

Responses are read with ResponseHeadersRead and deserialized straight off the network stream, so a large payload is never buffered into an intermediate string or byte array.

This type sits on the SDK's only BCL temporal boundary. HttpClient, SocketsHttpHandler, and the Retry-After header all traffic in TimeSpan, which no SDK can change. Constitution rule 12 therefore forbids naming the BCL type rather than pretending it does not exist: every crossing below is an inline conversion through NodaTime, so no BCL temporal type is ever declared. See the "Temporal types" section of CLAUDE.md for the full policy and the table of known boundary points.

Constructors

MassiveHttpTransport(MassiveClientOptions)

Creates a transport that owns its own HttpClient, configured from options.

public MassiveHttpTransport(MassiveClientOptions options)

Parameters

options MassiveClientOptions

The client configuration.

Exceptions

ArgumentNullException

options is null.

InvalidOperationException

options is incomplete.

MassiveHttpTransport(HttpClient)

Creates a transport over a caller-supplied HttpClient, for use with IHttpClientFactory. The caller keeps ownership of the client's lifetime, and is responsible for configuring its base address and authentication.

public MassiveHttpTransport(HttpClient httpClient)

Parameters

httpClient HttpClient

The configured client to send requests on.

Exceptions

ArgumentNullException

httpClient is null.

Methods

CreateHandlerPipeline(MassiveClientOptions, HttpMessageHandler)

Builds the handler pipeline this SDK sends through, wrapping primaryHandler with authentication and whatever resilience options asks for.

public static HttpMessageHandler CreateHandlerPipeline(MassiveClientOptions options, HttpMessageHandler primaryHandler)

Parameters

options MassiveClientOptions

The client configuration.

primaryHandler HttpMessageHandler

The innermost handler, which performs the actual transport.

Returns

HttpMessageHandler

The outermost handler, ready to hand to an HttpClient.

Remarks

The constructor taking a MassiveClientOptions uses this, and it is public so a caller supplying their own HttpClient can build the same pipeline instead of reconstructing it by hand. Order is the reason it is worth exposing rather than repeating.

Handlers compose authentication outermost, then retry, then the rate limiter. Authentication must be outermost because it rewrites the request URI under QueryString: a retry above it re-authenticates every attempt and appends the key once per try, which still succeeds and so is visible only as the key repeating in access logs (rule 11). The limiter is innermost because a retried attempt is a request the server counts, so it must spend a permit like any other (D30).

Exceptions

ArgumentNullException

options or primaryHandler is null.

InvalidOperationException

options is incomplete.

Dispose()

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

public void Dispose()

DownloadAsync(string, Stream, CancellationToken)

Issues a GET request and copies the response body to destination unchanged, for a route that serves a document rather than JSON.

public Task DownloadAsync(string requestUri, Stream destination, CancellationToken cancellationToken = default)

Parameters

requestUri string

The request URI, relative to the configured base address.

destination Stream

The stream the body is written to. The caller keeps ownership of it.

cancellationToken CancellationToken

A token to cancel the request.

Returns

Task

A task that completes once the whole body has been written.

Remarks

The content type is not inspected: the caller asked for the bytes, and the one route that needs this, the SEC filing file, names each file's type and size in its listing (decision D25). The body streams from the network into destination with no intermediate buffer, as every other response does. A failure partway through the copy -- the connection drops, or destination itself errors -- leaves the destination holding a partial body; nothing rewinds or truncates it, and the underlying exception propagates to the caller unchanged.

Exceptions

ArgumentNullException

requestUri or destination is null.

ArgumentException

requestUri is empty or whitespace, or destination is not writable.

ObjectDisposedException

This transport has been disposed.

MassiveRateLimitExceededException

The server responded with HTTP 429.

MassiveApiException

The server responded with any other error status.

EnumerateAsync<TEnvelope, TItem>(string, JsonTypeInfo<TEnvelope>, CancellationToken)

Issues a GET request and then follows the response's next_url cursor, yielding every item from every page.

public IAsyncEnumerable<TItem> EnumerateAsync<TEnvelope, TItem>(string requestUri, JsonTypeInfo<TEnvelope> typeInfo, CancellationToken cancellationToken = default) where TEnvelope : class, IPagedEnvelope<TItem>

Parameters

requestUri string

The first page's URI, relative to the configured base address.

typeInfo JsonTypeInfo<TEnvelope>

Source-generated metadata describing TEnvelope.

cancellationToken CancellationToken

A token to cancel the traversal.

Returns

IAsyncEnumerable<TItem>

Every item across every page, in the order the server returned them.

Type Parameters

TEnvelope

The paged response envelope type.

TItem

The result item type.

Remarks

Exactly one page is in flight at a time: the next request is issued only once the previous page has been fully consumed, so a caller who stops early stops the traffic too.

Cancellation is observed at page boundaries. The token is checked before each request, so a cancelled traversal issues no further request, but the items already deserialized from the page in hand are still yielded before the cancellation surfaces. Stopping mid-page is what break is for, and costs nothing on the traversals that never cancel.

A server that returns the same next_url twice in a row would make the traversal re-request one page forever, so that is detected and throws rather than followed. The check compares against the cursor just followed and nothing older, which keeps the traversal's own state constant however many pages it walks; the acknowledged cost is that a cycle through two or more distinct cursors is not caught. There is no page cap: any limit high enough to be safe for a genuine traversal is too high to bound a runaway usefully, and one low enough to bound it would truncate real results.

Exceptions

ArgumentNullException

requestUri or typeInfo is null.

ArgumentException

requestUri is empty or whitespace.

ObjectDisposedException

This transport has been disposed.

InvalidOperationException

A cursor was returned but the underlying client has no base address, so the cursor's origin cannot be checked. Raised while enumerating rather than from this call, since it depends on what the server sends back.

MassiveApiException

The server responded with an error status, or returned a cursor that is not a usable URI, points outside the configured base address, or repeats the cursor just followed.

GetAsync<T>(string, JsonTypeInfo<T>, CancellationToken)

Issues a GET request and deserializes the response body.

public Task<T?> GetAsync<T>(string requestUri, JsonTypeInfo<T> typeInfo, CancellationToken cancellationToken = default)

Parameters

requestUri string

The request URI, relative to the configured base address.

typeInfo JsonTypeInfo<T>

Source-generated metadata describing T.

cancellationToken CancellationToken

A token to cancel the request.

Returns

Task<T>

The deserialized response, or null when the body was empty.

Type Parameters

T

The response envelope type.

Exceptions

MassiveRateLimitExceededException

The server responded with HTTP 429.

MassiveApiException

The server responded with any other error status.

ThrowIfUnfollowableCursor(string?, string, string?)

Throws when a response offered a pagination cursor that this SDK cannot follow, because the operation's result is a single object rather than a page of items.

public static void ThrowIfUnfollowableCursor(string? nextUrl, string requestUri, string? requestId)

Parameters

nextUrl string

The response's next_url, or null when it sent none.

requestUri string

The request that produced the response, named in the exception.

requestId string

The response's request identifier, carried by the exception when present.

Remarks

Called by generated code for the operations whose OpenAPI success schema declares next_url on a result that is one object (decision D17). A blank cursor is the absence it means, as it is everywhere else in this transport. A real one is a page the caller will never receive, and missing data is reported loudly in this SDK rather than dropped.

Exceptions

ArgumentException

requestUri is empty or whitespace.

MassiveApiException

nextUrl is a cursor.