Skip to content
ATLAS API / DEVELOPER REFERENCE

Developer documentation · V1 preview

Official-source records.
One API key.

Read structured news, publisher release schedules, source health and immutable event revisions through the same canonical data API used by the Atlas terminal. No client ID, second REST secret or OAuth refresh flow.

11 read routesSources, news & schedules
120 / minutePer customer API key
One keyStandard Bearer header

Customer API access requires an active Pro plan. Paid subscriptions are not yet open. The customer base URL is https://www.olympusatlas.com/api/v1. A hosted staging preview is not a public paid offer. External alert dispatch is disabled; no latency or availability SLA is offered in this preview.

Make your first request

  1. Sign in and open Account → Integrations. Customer REST access requires an active Pro plan.
  2. Create a key with a meaningful label and expiry. Copy the single oa_… string shown once; store it as OLYMPUS_ATLAS_API_KEY in your backend’s private environment.
  3. Send it as Authorization: Bearer <your-key>. The base URL is an address, not another credential.
# Set OLYMPUS_ATLAS_API_KEY in your private environment.
curl --fail-with-body --get \
  "https://www.olympusatlas.com/api/v1/source-events" \
  -H "Authorization: Bearer $OLYMPUS_ATLAS_API_KEY" \
  -H "Accept: application/json" \
  --data-urlencode "limit=25" \
  --data-urlencode "order=desc" \
  --data-urlencode "source_classification=live"

Examples use curl, Python’s standard library and Node’s built-in fetch. They read live-classified sources, not necessarily events that are fresh or deliverable. An empty data array is a valid response. Never put the key in a website’s client bundle.

Historical quotes & orders

Historical purchasing is not open in this preview. When enabled, your API key can request an exact, no-charge quote with POST /historical-quotes, then obtain a Stripe-hosted payment link with POST /historical-orders. The key cannot charge a saved card. A person must complete Checkout; verified payment and private fulfillment are required before access starts. Reuse the same idempotency key if a Checkout request times out. Quotes expire after 30 minutes and changed catalogs require a new quote.

curl --fail-with-body -X POST "https://www.olympusatlas.com/api/v1/historical-quotes" -H "Authorization: Bearer $OLYMPUS_ATLAS_API_KEY" -H "Content-Type: application/json" -d '{"dataset_id":"official_macro_events","start_date":"2026-01-01","end_date":"2026-02-01"}'

# Review quote.amount_cents and billable_event_count before proceeding.
# Replace the placeholders below with the returned quote ID and your own
# stable 16–100 character retry key. Open checkout_url in a browser.
curl --fail-with-body -X POST "https://www.olympusatlas.com/api/v1/historical-orders" -H "Authorization: Bearer $OLYMPUS_ATLAS_API_KEY" -H "Content-Type: application/json" -d '{"quote_id":"<quote-id>","idempotency_key":"<stable-retry-key>"}'

An API key can then list only its owner’s latest 100 orders at GET /historical-orders and repeat-download a ready order at /historical-orders/{orderId}/download. The immutable ZIP is available for 30 days from readiness, not purchase time. Expiry, refund, dispute or revocation ends access. A different account’s key cannot retrieve the order.

These routes are omitted from the downloadable OpenAPI document until customer historical access is enabled and the paid-order acceptance gate passes. The whole eligible event count sets the order price at the current plan’s quoted curve; API calls and repeat downloads are not pricing units. The first event is $1 on every eligible plan, and larger orders have lower average prices. Source/publisher weighting is not applied.

Authentication & key lifecycle

Authorization: Bearer oa_<your-key>
Accept: application/json

The prefix identifies an Atlas credential; everything after it is part of the same secret. Key IDs, labels and redacted prefixes are management metadata—you do not send those to authenticate. A Supabase publishable/secret key, Google login token or Stripe key will not work.

Atlas-Api-Key: <the-same-key> remains a backward-compatible alternative. Send exactly one supported header, not both; ambiguous or malformed credentials are refused.

Up to five active keys per customer. Choose expiry from 1–365 days (the account form defaults to 90). Pro access, scope, expiry and revocation are checked on each request. Rotation creates a replacement and revokes the old key atomically; update your integration immediately. Lost keys cannot be retrieved—revoke and replace. If an issuance response is lost, revoke the orphan key shown in your key list.

Keys authorize events:read only on the documented customer surface. They do not grant admin, execution, proprietary strategy, market-price or programmatic webhook-management access. Never send keys in query strings, tickets, screenshots or logs. Revoking a key does not cancel billing.

Endpoint reference

All paths below are relative to https://www.olympusatlas.com/api/v1. Read responses are JSON except historical ZIP downloads. Filter values are single, exact-match query values unless described otherwise. Discover identifiers through /sources and the event payload; do not guess provider names.

List sources

GET/sources

Discover physical source IDs, publisher policies, classifications and observed health.

Response shape: SourcePage in the OpenAPI specification.

ParameterType / allowed valuesMeaning
cursor
query · optional
stringOpaque next_cursor from the same endpoint and filter scope. Omit on the first request.
limit
query · optional
integer
Default: 50
Page/observation size: 1–500, default 50.
publisher_id
query · optional
stringPublisher identifier obtained from a source.
release_family
query · optional
stringRegistered release-family identifier obtained from an event or source.
source_classification
query · optional
fixture, historical, liveExact-match filter; omit to include all supported values.
certification_state
query · optional
fixture, provisional, certified, degradedExact-match filter; omit to include all supported values.

Source details

GET/sources/{source_id}

Current source policy, registry versions and observed health.

Response shape: SourceDetail in the OpenAPI specification.

ParameterType / allowed valuesMeaning
source_id
path · required
stringPhysical source ID obtained from /sources.

Source health

GET/sources/{source_id}/health

Bounded observations for one physical source. Unknown is not healthy.

Response shape: Health in the OpenAPI specification.

ParameterType / allowed valuesMeaning
source_id
path · required
stringPhysical source ID obtained from /sources.
as_of
query · optional
stringISO-8601 UTC instant: return records knowable at this cutoff, not a claim of original publisher vintage.
dimension
query · optional
reachability, freshness, schedule_adherence, clock_consistency, structure, parse, extraction, summaryExact-match filter; omit to include all supported values.
limit
query · optional
integer
Default: 50
Page/observation size: 1–500, default 50.

Network health

GET/source-health

Aggregate observed source health; not a platform uptime or latency guarantee.

Response shape: Health in the OpenAPI specification.

ParameterType / allowed valuesMeaning
as_of
query · optional
stringISO-8601 UTC instant: return records knowable at this cutoff, not a claim of original publisher vintage.

Release calendar

GET/calendar

Publisher-stated schedule projection. Date filters use scheduled time, with inclusive bounds.

Response shape: CalendarPage in the OpenAPI specification.

ParameterType / allowed valuesMeaning
include_date_only
query · optional
boolean
Default: false
Opt into verified date-only initial schedule slots when using from/to. Their publisher calendar dates are compared with the inclusive ISO date portions of the bounds. Unknown/rescheduled untimed records are excluded; no UTC instant is invented. Cursor scope includes this selection.
cursor
query · optional
stringOpaque next_cursor from the same endpoint and filter scope. Omit on the first request.
limit
query · optional
integer
Default: 50
Page/observation size: 1–500, default 50.
from
query · optional
stringInclusive scheduled-time lower bound, ISO-8601 UTC. Undated rows excluded when a range is requested.
to
query · optional
stringInclusive scheduled-time upper bound, ISO-8601 UTC. Must not precede from.
source_id
query · optional
stringPhysical source ID obtained from /sources.
event_scope_id
query · optional
stringLogical release stream; may span different physical sources.
release_family
query · optional
stringRegistered release-family identifier obtained from an event or source.
status
query · optional
stringDerived calendar status: announced, rescheduled, published, cancelled or expired. Revised/corrected/retracted lifecycles derive published; rescheduled applies only to announced events.
source_classification
query · optional
fixture, historical, liveExact-match filter; omit to include all supported values.
as_of
query · optional
stringISO-8601 UTC instant: return records knowable at this cutoff, not a claim of original publisher vintage.

List events

GET/source-events

Latest known version per event lineage, or the version visible at as_of. Ordered by effective availability and version ID.

Response shape: EventPage in the OpenAPI specification.

ParameterType / allowed valuesMeaning
cursor
query · optional
stringOpaque next_cursor from the same endpoint and filter scope. Omit on the first request.
limit
query · optional
integer
Default: 50
Page/observation size: 1–500, default 50.
source_id
query · optional
stringPhysical source ID obtained from /sources.
event_scope_id
query · optional
stringLogical release stream; may span different physical sources.
publisher_id
query · optional
stringPublisher identifier obtained from a source.
release_family
query · optional
stringRegistered release-family identifier obtained from an event or source.
topic
query · optional
inflation, labor, growth, liquidity, rates_policy, fiscal_policy, trade, financial_stability, otherExact-match filter; omit to include all supported values.
min_urgency
query · optional
routine, notable, high, criticalExact-match filter; omit to include all supported values.
lifecycle
query · optional
announced, published, revised, corrected, retracted, cancelled, expiredExact-match filter; omit to include all supported values.
schedule_status
query · optional
scheduled, unscheduled, rescheduled, schedule_unknownExact-match filter; omit to include all supported values.
processing_state
query · optional
complete, degraded, parse_failed, summary_abstained, review_requiredExact-match filter; omit to include all supported values.
delivery_eligibility
query · optional
eligible, withheld_reinterpretation, withheld_quality, withheld_rights, withheld_schedule_noise, withheld_context, eligible_to_prior_recipients, withheld_no_prior_deliveryExact-match filter; omit to include all supported values.
publication_only
query · optional
boolean
Default: false
Only published, revised, corrected and retracted lineages. Combine with a pinned as_of cutoff for a current-news queue; default false preserves corpus browsing, including notices. Retractions are warnings, not publishable stories.
substantive_only
query · optional
boolean
Default: false
Require an anchored non-null numeric claim other than publication/document. Depth filter only—not semantic verification, freshness, source health, rights or editorial clearance. Retractions with retained facts remain warnings; use an unfiltered change poll too.
deliverable_only
query · optional
boolean
Default: false
Filter by event eligibility; not proof a specific customer has received or can receive it.
reference_period
query · optional
stringExact provider reference-period identifier.
source_classification
query · optional
fixture, historical, liveExact-match filter; omit to include all supported values.
order
query · optional
asc, desc
Default: asc
Exact-match filter; omit to include all supported values.
as_of
query · optional
stringISO-8601 UTC instant: return records knowable at this cutoff, not a claim of original publisher vintage.

Event details

GET/source-events/{source_event_id}

Canonical event, provenance, evidence, adjacent versions, prior comparable values and change set where available.

Response shape: EventDetail in the OpenAPI specification.

ParameterType / allowed valuesMeaning
source_event_id
path · required
stringStable event lineage ID obtained from an event.
as_of
query · optional
stringISO-8601 UTC instant: return records knowable at this cutoff, not a claim of original publisher vintage.

Event history

GET/source-events/{source_event_id}/versions

All immutable versions visible at as_of, ordered by event-version ordinal ascending.

Response shape: EventPage in the OpenAPI specification.

ParameterType / allowed valuesMeaning
source_event_id
path · required
stringStable event lineage ID obtained from an event.
as_of
query · optional
stringISO-8601 UTC instant: return records knowable at this cutoff, not a claim of original publisher vintage.
cursor
query · optional
stringOpaque next_cursor from the same endpoint and filter scope. Omit on the first request.
limit
query · optional
integer
Default: 50
Page/observation size: 1–500, default 50.

Exact version

GET/source-events/{source_event_id}/versions/{source_event_version_id}

One immutable version belonging to the specified lineage. No as_of parameter.

Response shape: VersionDetail in the OpenAPI specification.

ParameterType / allowed valuesMeaning
source_event_id
path · required
stringStable event lineage ID obtained from an event.
source_event_version_id
path · required
stringImmutable sev_ version ID from event history.

List SEC filing packages

GET/sec/filing-packages

Group independently anchored SEC facts by CIK and accession. Exact periods remain separate; conflicting values are withheld. Prior-year comparisons require the same issuer, form family, subject, measure, unit and comparable exact duration. Packages are not earnings announcements or original filing vintages.

Response shape: FilingPackagePage in the OpenAPI specification.

ParameterType / allowed valuesMeaning
cursor
query · optional
stringOpaque next_cursor from this endpoint with the identical filter scope.
limit
query · optional
integer
Default: 50
Page size: 1–500, default 50.
issuer_cik
query · optional
stringSEC CIK, padded or unpadded.
accession
query · optional
stringExact SEC accession in 0000000000-00-000000 format.
filing_form
query · optional
10-K, 10-Q, 10-K/A, 10-Q/AVerified filing form.
content_depth
query · optional
filing_package_sparse, filing_package_partial, filing_package_core_results, filing_package_deepMechanical fact coverage, not editorial or publication clearance.
min_supported_facts
query · optional
integer
Default: 1
Minimum deduplicated, anchored, non-conflicting facts.
min_distinct_metrics
query · optional
integer
Default: 1
Minimum distinct declared SEC financial metrics.
include_conflicts
query · optional
boolean
Default: true
Include packages with disclosed conflicts; conflicted observations never enter supported facts.
order
query · optional
asc, desc
Default: desc
Order by filed date and stable package ID.
as_of
query · optional
stringISO-8601 UTC knowledge cutoff applied to every component event.

SEC filing package details

GET/sec/filing-packages/{package_id}

Return exact fact cohorts, source-event version and claim references, disclosed conflicts, mechanically comparable prior-year facts and SEC filing link. No source prose is returned.

Response shape: FilingPackageDetail in the OpenAPI specification.

ParameterType / allowed valuesMeaning
package_id
path · required
stringStable sfp_ package ID returned by the package list.
as_of
query · optional
stringISO-8601 UTC knowledge cutoff applied to every component event.

Pagination, revisions & historical research

Paginated sources, events, calendar and version history return data, count, has_more and next_cursor. count is this page’s size, not a total-dataset count. Default page size is 50; maximum is 500. Health reads are bounded observations or aggregates, not cursor pages.

{
  "object_kind": "source_event",
  "data": [],
  "count": 0,
  "has_more": false,
  "next_cursor": null,
  "as_of": null,
  "sources": {},
  "documents": {},
  "source_contract_version": "OA-C005",
  "time_contract_version": "OA-C001",
  "data_is_fixture": false,
  "fixture_notice": null
}

Publisher metadata and story depth

Lists and history include a research_assessments map keyed by immutable event-version ID. Details includeresearch_assessment. Read content_depth, fact counts and comparison claim IDs beside the canonical event. These labels mean anchored extraction, not independent fact-checking or permission to publish. Optional substantive_only=trueselects numeric-fact records; it does not imply a complete story.

Event lists, details and version lists expose an additive documents map, keyed by the event’s evidence.document_version_id. It resolves the pinned title, original document URL, document type, publication revision, publisher time and Atlas first-seen time. It never changes canonical event bytes. Publisher time can be null. Current link-only policies omit title text; excerpt policies cap it. Metadata does not grant article reproduction rights.

A publication claim with measure publication, unit document and no reported figure is a discovery notice—not enough material for a financial explainer. Non-null financial claims provide extracted facts; comparisons require the same stream, subject, measure, claim type and unit, with explicit reporting periods. Confidence describes processing/evidence, not a market direction. Read Evidence and permissions before publishing; Orion should retain a review queue.

Weekly DOL claims now includes seasonally adjusted headline levels, source-stated weekly changes and available original/revised prior figures. Initial claims and continuing claims refer to different weeks; use each claim’s period. Percent rate levels and percentage-point changes are different units. Coverage is specific to this report’s headline prose, not every table or state. Filing and speech families may remain discovery-only. Claim values are integers, decimal strings or null—not free-form prose.

Health checks and release freshness

Health exposes each check’s state, observation time, reason and whether it was checked. Unknown is not a failure or success. The strict healthy flag still requires all eight dimensions to be explicitly OK. Schedule applicability is separate: not applicable or not configured never silently turns unknown into OK. A validated fact can coexist with an unchecked schedule; your review policy must decide which checks are required, not infer that from a legacy confidence label.

For calendars, include_date_only=true includes verified untimed initial schedule slots inside date bounds. Read scheduled_date, time_precision and schedule_timezone. A date-only meeting has null timestamp fields. Do not schedule a timed post from an assumed release hour; genuinely undated or rescheduled untimed records remain unknown.

Pass next_cursor back as cursor while has_more is true. Treat it as opaque: don’t edit it, move it to another endpoint or change selecting filters/order/cutoff. Use URL encoding. Cursors are not a persistent live-feed subscription.

For news available now, use publication_only=true,source_classification=live, order=descand a current UTC as_of cutoff, pinned across pages. This excludes announcement, cancellation and expiry notices. Retractions remain in the queue as warnings: never republish them as active facts. The unfiltered endpoint browses the research corpus, including upcoming notices; use Calendar for release planning. “Available” refers to Atlas knowledge, not a guarantee of story depth, permissions, delivery eligibility or complete publisher coverage.

A cutoff is not a recency window. Check the pinned document’spublished_at against your lookback before calling a record breaking news. Null publisher times require another review path. Atlas first-seen and processing availability are not substitutes for publisher time. Reprocessing an old release can create a newly available interpretation without a new publisher release.

For reproducible event/history traversal, pin an as_of UTC cutoff and retain it across every page. The default event list is the head version per lineage; it is not a complete revision log. Use /source-events/{id}/versions to reconcile corrections, retractions and other changes. Store immutable version IDs and do not silently overwrite prior evidence.

Poll changes without substantive_only ordeliverable_only as well: corrections can remove facts and retractions must reach your review queue. Compare stored immutable version IDs, then retrieve the lineage’s versions to reconcile missed intermediate changes. Start each polling sweep without an old cursor, pin a new cutoff, and overlap your retained monitoring window.atlas_reprocessing is not a publisher correction and is normally withheld from new-alert delivery.

import json, os, time
from datetime import datetime, timezone
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

base = os.getenv("OLYMPUS_ATLAS_API_BASE_URL", "https://www.olympusatlas.com/api/v1")
headers = {"Authorization": "Bearer " + os.environ["OLYMPUS_ATLAS_API_KEY"]}
# Pin the cutoff and keep every selecting filter unchanged for this traversal.
query = {"limit": 100, "order": "asc", "source_classification": "live",
         "as_of": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")}

def get_page():
    for attempt in range(4):
        try:
            request = Request(base + "/source-events?" + urlencode(query),
                              headers=headers)
            with urlopen(request, timeout=15) as response:
                return json.load(response)
        except HTTPError as error:
            if error.code != 429 or attempt == 3:
                raise
            time.sleep(max(1, int(error.headers.get("Retry-After", "60"))))

for _ in range(1000):  # Explicit client safety bound; not an Atlas page limit.
    page = get_page()
    for event in page["data"]:
        print(event["source_event_version_id"])  # Upsert/deduplicate this ID.
    if not page["has_more"]:
        break
    if not page["next_cursor"]:
        raise RuntimeError("Missing continuation cursor")
    query["cursor"] = page["next_cursor"]
else:
    raise RuntimeError("Traversal safety bound reached; save progress")

Historical provider snapshots are useful for research, but their Atlas availability starts when Atlas actually acquired them. An archive acquired today is not evidence Atlas operated years ago or knew today’s revised figures at the original release time. Event as_of does not promise a complete point-in-time vintage of every adjacent source-policy field.

Read the data correctly

Event pages contain canonical payloads in data[]. Event/detail-version responses contain the same event object in event, with provenance and source metadata beside it. Calendar rows are schedule projections—not full canonical events. Aggregate health returns sources[]; per-source health returns states and bounded observations[].

Canonical fieldInterpretation
source_event_idStable event lineage. A later correction does not create a new story identity.
source_event_version_id / event_versionImmutable version ID and increasing ordinal. Deduplicate/upsert by version ID; use /versions to reconcile changes.
sourcePhysical source ID, logical event scope and pinned registry version. Consult the response’s adjacent source metadata for attribution and rights.
lifecycle / version_cause / version_actorAnnounced, published, revised, corrected, retracted, cancelled or expired; distinguish publisher changes from Atlas reinterpretation.
summary / claimsObservational summary and structured claims. Values can be unavailable; do not fill missing numbers or invent consensus.
prior_comparable / change_setComparison and change metadata when supported. Null is meaningful, not zero or “unchanged.”
times / availabilityDetection, revision, schedule and effective availability—not the same clock. UTC ISO strings and decimal-string epoch nanoseconds preserve point-in-time truth.
confidence / evidence_qualityEvidence/interpretation quality, not directional trading confidence or a probability of profit.
delivery.eligibilityEligibility or a withholding reason. Eligibility alone is not a customer receipt or dispatch guarantee.
payload_fingerprintSHA-256 canonical payload fingerprint. Exact raw bytes matter for webhook verification; arbitrary JSON serialization is not canonical.

Preserve decimal strings and nanosecond strings; do not coerce them into floating-point numbers. A null schedule, number or comparison is unknown/unavailable, not a zero. Read fixture flags, source classification, processing state, evidence quality, rights and delivery eligibility together.

source_classification=live classifies a source, not an event’s age or delivery status. deliverable_only=true filters record eligibility but does not activate a rule or prove a receipt. Context-only, archived, fixture, rights-held and Atlas-reinterpretation records must not be presented as fresh publisher alerts.

Attribution and redistribution restrictions remain applicable to public first-party information. Access does not license every underlying article for republication. Link to the official source and respect its current policy. Raw provider bodies and credentials are not exposed. Unknown health is not healthy. Atlas publishes observations, not guaranteed trades or price forecasts.

V1 URLs are versioned under /api/v1; canonical events identify their own contracts. Clients should tolerate additional fields and fail safely on unfamiliar statuses. Downloadable OpenAPI response schemas document selected fields, not an exhaustive typed SDK or a compatibility SLA.

Errors, limits & polling

The customer budget is 120 requests per fixed UTC minute per key, durably counted. This is not a rolling-minute window or guaranteed throughput. Successful authorized responses include Atlas-RateLimit-Limit and Atlas-RateLimit-Remaining. A quota refusal includes Retry-After in seconds.

Start with modest polling (for example every 15–30 seconds), bounded pages and retries. Fetch individual histories when a lineage changes rather than repeatedly downloading the full archive. Source cadence varies; polling faster does not make a publisher release data sooner. Signed webhooks are the push option, subject to release activation.

HTTPMeaningAction
400Invalid filters, cursor or cutoffCorrect the parameters. On invalid_cursor, restart that traversal without a cursor.
401Credential refusedCheck the whole key, exactly one auth header, expiry/revocation and current Pro entitlement. Do not retry unchanged.
403Paid authorization refusedCheck access. An API key cannot authorize admin or proprietary strategy operations.
404Not found / not knowable at cutoffVerify source, lineage and version IDs, path and as_of.
405Method not allowedUse the documented method. Historical quote and hosted Checkout POST routes remain gated.
422Type or bounds invalidUse limit 1–500 and correctly typed query values.
429Key rate budget exhaustedWait the Retry-After seconds, then retry with bounded backoff.
500 / 503Request failed / integration unavailableRetry with bounded backoff; retain Atlas-Request-Id when returned. No availability SLA is promised in preview.
{
  "error": "Key is inactive, expired, revoked or not entitled."
}

Check the HTTP status first. Gateway errors use a readable error string; canonical errors use error.code, error.message and error.request_id. Preserve request IDs when available, but redact authentication headers. Network timeouts can occur without a JSON response.

Signed event webhooks

Pro customers configure a destination and filters in Integrations. Rules start disabled. A webhook signing secret is separate from your REST key because it verifies messages sent to you—it is not a second credential required for API requests. Copy it once into the receiving server’s secret manager.

Destinations require public HTTPS on port 443, no URL credentials/fragments, and public DNS. Atlas revalidates public addresses, pins the TLS connection and does not follow redirects. Localhost and private-network destinations are not accepted. Activation only covers newly recorded, effectively available, eligible records; it never sends the entire archive.

Atlas-Signature: t=<unix-seconds>,v1=<hex-hmac>
Atlas-Delivery-Id: <stable-delivery-id>
Atlas-Event-Id: <lineage-id>
Atlas-Event-Version-Id: <immutable-version-id>
Atlas-Attempt: <attempt-number>

signed_bytes = UTF8("v1." + timestamp + ".") + exact_raw_body
signature = HMAC-SHA256(signing_secret, signed_bytes)

The body is the exact canonical event object, not a REST-page envelope. Verify the raw request bytes before JSON parsing, compare signatures in constant time and reject timestamps outside ±5 minutes.

import hashlib, hmac, re, time

def verify_atlas(raw_body: bytes, signature_header: str, signing_secret: str):
    match = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", signature_header)
    if not match:
        raise ValueError("Malformed signature")
    timestamp, signature = match.groups()
    if abs(int(time.time()) - int(timestamp)) > 300:
        raise ValueError("Stale or future signature")
    signed_bytes = ("v1." + timestamp + ".").encode("utf-8") + raw_body
    expected = hmac.new(signing_secret.encode("utf-8"), signed_bytes,
                        hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature):
        raise ValueError("Invalid signature")
    # Parse JSON only now. Transactionally deduplicate Atlas-Delivery-Id.
    # Return 2xx only after durable acceptance, including known duplicates.

Delivery is at least once: transactionally deduplicate Atlas-Delivery-Id. Retries retain that ID. Return 2xx only after durable acceptance; five automatic attempts use bounded exponential backoff and deterministic jitter. Exhausted attempts remain visible in account delivery history for eligible owner recovery. No persistent customer streaming or programmatic rule-management endpoint is advertised in V1.

Rights, current paid access, destination, suppression, filters and event availability are checked again before sends. Prior-recipient withdrawals require an actual delivered receipt for that lineage/rule. Email goes only to the verified account address; provider acceptance is not delivery or an inbox guarantee.

External customer dispatch is disabled in this private preview. Configuring a rule does not prove that outbound delivery is active or that production callbacks have passed acceptance.