Overview
SparkVault Notify delivers notifications that are verified, sealed, and provable, not plaintext blasts. A notification reaches the right person, proves they read/approved/signed it with a portable receipt, keeps replies encrypted, and stays recallable. Notify is composed from the SparkVault primitives rather than bolted onto them: every send is one sealed Spark behind a single-use SparkLink, opened through an Identity verification ceremony, with Notify owning only transport and orchestration.
The Four-Layer Model
Notify never touches crypto directly. Each layer owns one job, so confidential content stays sealed end to end and the transport only ever carries an opaque pointer.
| Layer | Owns |
|---|---|
| Spark | Sealed content + lifecycle (double-zero-trust, view-once, TTL, burn-after-read). Knows nothing about recipients. |
| SparkLink | The per-recipient verified-access grant: verification_level × interaction, single-use, revocable. Emits the signed receipt. |
| Identity | The verification ceremony (auth.sv) producing an EdDSA token, with action_hash binding for approve/sign. |
| Notify | Transport + orchestration: channels, escalation, fan-out, preferences, inbox, receipts, billing. Carries only the SparkLink pointer. |
Notification content is sealed per recipient as a Spark behind a single-use SparkLink. List and feed
responses are metadata-only and never carry plaintext. The recipient opens the
sparklink_code pointer to unseal. A partial or failed send hard-recalls every grant it minted,
so a broadcast never leaves orphaned sealed content behind.
Base URL & Authentication
All Notify endpoints are served under a single base path. The tenant is taken from your authenticated account token. There is no account ID in the path.
https://api.sparkvault.com/v1/products/notify
Authentication
Every endpoint is account-token authed (a registered user). Pass a session JWT or an API key; both resolve to the calling account, which scopes tenant isolation on every read and write.
| Method | Header | Format |
|---|---|---|
| JWT | Authorization | Bearer {token} |
| API Key | X-API-Key | sv_live_{token} |
Response Envelope
Every success response wraps its payload in a data envelope with a meta sibling
carrying the deployed API build version and server timing. The field tables on this page describe the
contents of data. Error responses carry an error object with a
meta sibling carrying api_version (see Error Handling).
{
"data": { /* endpoint payload: documented per endpoint below */ },
"meta": { "api_version": "1.2.828", "request_id": "...", "response_ms": 12, "timestamp": 1719446400 }
}
The inbox, status, and receipts read surface is sender-side: an account reads and manages the
notifications it sent, proxying its recipients' inbox interactions through its own backend. A caller never
sees another account's rows. A cross-tenant send_id or notification is reported identically to one
that never existed (HTTP 404).
Quick Start
Send your first sealed notification with a single POST. Notify seals the content for each recipient, mints a
single-use SparkLink, and returns a send_id you can poll for delivery status and receipts.
Send a sealed notification
curl -X POST 'https://api.sparkvault.com/v1/products/notify/send' \
-H 'X-API-Key: sv_live_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"recipients": [
{ "email": "user@example.com" }
],
"title": "Your invoice is ready",
"category": "transactional",
"content": {
"payload": "Invoice #1043 for $129.00 is attached.",
"content_type": "text/plain"
},
"policy": { "verification_level": "identifier", "interaction": "view" },
"channels": ["email", "push"]
}'
const res = await fetch('https://api.sparkvault.com/v1/products/notify/send', {
method: 'POST',
headers: {
'X-API-Key': process.env.SPARKVAULT_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
recipients: [{ email: 'user@example.com' }],
title: 'Your invoice is ready',
category: 'transactional',
content: {
payload: 'Invoice #1043 for $129.00 is attached.',
content_type: 'text/plain'
},
policy: { verification_level: 'identifier', interaction: 'view' },
channels: ['email', 'push']
})
});
const { data } = await res.json();
console.log(data.send_id, data.recipients, data.status); // "ntsnd_01...", 1, "pending"
import os, requests
res = requests.post(
'https://api.sparkvault.com/v1/products/notify/send',
headers={'X-API-Key': os.environ['SPARKVAULT_API_KEY']},
json={
'recipients': [{'email': 'user@example.com'}],
'title': 'Your invoice is ready',
'category': 'transactional',
'content': {
'payload': 'Invoice #1043 for $129.00 is attached.',
'content_type': 'text/plain'
},
'policy': {'verification_level': 'identifier', 'interaction': 'view'},
'channels': ['email', 'push']
}
)
res.raise_for_status()
data = res.json()['data']
print(data) # { "send_id": "ntsnd_01...", "recipients": 1, "status": "pending" }
Notify sealed the body into a Spark, minted a single-use SparkLink for the recipient, and wrote one send row. The recipient receives a pointer on each channel (“tap to view”); when they open it and verify their identifier, the content unseals and a signed receipt is recorded.
Sending Notifications
The send endpoint is the primary entry point. It seals + mints one single-use SparkLink per recipient synchronously from the in-memory payload (so nothing unsealed is ever persisted), then writes the send row whose stream drives fan-out and delivery.
/products/notify/send
Create a secure send: seal content per recipient, mint a single-use SparkLink each, and write the send row that drives fan-out + delivery.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
recipients |
object[] | string[] | Required | Non-empty audience. Each entry is a bare identifier string or { id?, email?, phone? }. An SVID id (ing_...) enables stored recipient preferences. Max 500 per send; duplicates are collapsed. |
content |
object | Optional | Content to seal per recipient: { payload, content_type?, filename?, ttl_minutes? }. Provide exactly one of content OR ingot. |
ingot |
object | Optional | Existing sealed asset reference { ingot_id, vault_id }. 1:1 sends only (one persistent asset = one grant). Provide exactly one of content OR ingot. |
policy |
object | Optional | Access policy for the minted SparkLink: { verification_level?, interaction?, reveal_freshness_minutes? }. See the policy table below. |
channels |
string[] | Optional | Delivery-channel override. When omitted, the company config resolves them (per-category channel_priority then default_channels). |
escalation |
object | Optional | { delays: number[] } cumulative per-step delays in seconds. When omitted, derived from the resolved channels + company config. |
title |
string | Optional | Channel-agnostic display title (metadata; never sealed). |
category |
string | Optional | The configured category this send is classified as (drives channel resolution, recipient preferences, and the mandatory-seal floor). |
type |
string | Optional | Optional display type for the recipient feed. |
history_ttl_days |
integer | Optional | Inbox-row retention in days.Default: 30 |
send_id |
string | Optional | Caller-supplied idempotency key. Generated (ntsnd_...) when absent; a retry with the same id returns the existing send untouched. |
policy Object
| Parameter | Type | Required | Description |
|---|---|---|---|
verification_level |
string | Optional | "none" | "identifier" | "passkey" | "out_of_band" | "dual_control". How strongly the recipient must prove identity before the content unseals. |
interaction |
string | Optional | "view" | "acknowledge" | "sign" | "approve" | "reply". The interaction the SparkLink requires; sign/approve bind an action_hash into the receipt. |
reveal_freshness_minutes |
integer | Optional | Require a verification no older than this many minutes before the content reveals. Must be a positive integer (0 is rejected); omit the field (or pass null) for no freshness requirement. |
Response
| Field | Type | Description |
|---|---|---|
send_id |
string | The send identifier (ntsnd_...). Use it to query status and receipts. |
recipients |
integer | Number of distinct recipients sealed for this send. |
status |
string | Write-once "pending": the send was accepted and fan-out is driven asynchronously. |
idempotent |
boolean | Present and true when an existing send_id was returned as-is (no re-seal). |
{
"data": {
"send_id": "ntsnd_01j9z4f6w8m3qk2c7d5h0abxyz",
"recipients": 1,
"status": "pending"
},
"meta": { "api_version": "1.2.828", "request_id": "...", "response_ms": 412, "timestamp": 1719446400 }
}
Provide exactly one of content or ingot. An ingot is one persistent
sealed asset with a single grant, so it can only target a single recipient; use content to fan a
fresh per-recipient spark out to many.
A single send targets at most 500 recipients. The audience rides inline on the send row and each recipient is sealed synchronously, so split a larger blast into multiple sends.
Delivery Model: Sealed vs Plaintext
Delivery mode is a property of the message's nature, not a runtime flag. There is one send front door; how the content travels depends on whether it is confidential.
Sealed
Content is sealed into a Spark behind a single-use SparkLink. Channels carry only the pointer; the recipient verifies and unseals. The secure-reveal path for any confidential content.
- No plaintext at rest
- Works on every channel
- Produces a verified receipt
Plaintext
Non-confidential content (an alert, a welcome) is delivered inline in the channel payload: no seal, no SparkLink, no reveal step. A constrained, fail-closed mode for low-stakes transactional messages.
- Inline-capable channels only (email)
- Max 50 recipients, 7-day TTL
text/plainortext/htmlonly
The v1 REST send surface is sealed-only: POST /products/notify/send carries no
delivery selector, so every API send travels sealed. Plaintext is the engine's mode for first-party platform
flows (for example the alert preset's inline pings), and its constraints are enforced
fail-closed: content only (never an ingot), text/plain or
text/html, inline-capable channels only (email today), at most 50 recipients, and the send row's
retention capped at 7 days.
A hardcoded baseline of confidential categories (secure, conversation,
approval, signature) always seals and can never be delivered
plaintext. A tenant's security.mandatory_seal_categories may only expand that set, never
shrink it, so a confidential category can never be demoted to cleartext by a config edit. A plaintext send
that targets a sealing-required category, a pointer-only channel, or too large an audience is rejected
(fail-closed), never silently downgraded.
Presets
Presets are five secure-by-default bundles that pair a policy (verification_level ×
interaction) with a sensible default channel ladder and a display category. They are server-side
helpers used by first-party platform flows (there is no preset parameter on the send endpoint),
so treat the table below as the recommended bundles to assemble in your own request body. Caller-supplied
fields always win over a preset's defaults; an omitted channels lets the company config resolve
them per category.
| Preset | verification_level | interaction | Default channels |
|---|---|---|---|
alert | none | view | config-resolved (plaintext) |
secureMessage | identifier | view | push, email |
conversation | identifier | reply | in_app, push, email |
approval | passkey | approve | in_app, push, email |
signatureRequest | passkey | sign | in_app, push, email |
A preset's category (e.g. approval, signature) is an interaction bundle.
The high-stakes presets map to the hardcoded confidential categories, so an approval or
signatureRequest always seals regardless of any config edit. alert is the one
plaintext bundle: it delivers a non-confidential ping inline rather than behind a tap-to-view link; over the
REST API an alert-style send travels sealed like everything else.
Channels & Escalation
Every channel is a dumb pointer-carrier behind one provider seam. No channel ever sees plaintext. The durable inbox row is the source of truth; a WebSocket push is only a best-effort realtime nudge.
Supported Channels
push is APNs/FCM; web_push is VAPID. Per-app channel credentials live in the
owner-managed config and are never account-writable.
Verified-Seen-Aware Escalation
When escalation_enabled, each step fires only if the prior step's notification is still unseen.
Escalation is the next-channel delivery enqueued with a delay; the chain stops the moment the recipient's
verified seen_at is recorded, so a recipient who reads the email is never also called.
{
"channels": ["push", "email", "voice"],
"escalation": { "delays": [0, 120, 600] }
}
Cumulative per-step delays in seconds: push immediately, email at 2 minutes if still unseen, a voice call at
10 minutes if still unseen. Omit escalation to derive delays from the company config's
per-channel realtime_fallback_delay.
When the config sets delivery.escalation_enabled: false, the resolved ladder collapses to the
primary channel only: no fallback steps fire. delays[0] is always forced to 0 (an
override's first entry is ignored), and the stored schedule is normalized to the resolved channel count:
extra entries are dropped, a missing tail is derived from the config, and descending values are clamped so
the schedule is always non-decreasing.
Inbox
The inbox is a recipient's per-notification feed, metadata only. Each row carries the SparkLink pointer so the client opens the link to unseal; the plaintext and the recipient's raw contact are never echoed here.
Read a Recipient's Feed
/products/notify/inbox
A recipient's metadata-only feed, newest first. Account-scoped: returns only notifications THIS account sent to the recipient.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_id |
string | Required | The inbox owner (the recipient identifier the send addressed). |
state |
string | Optional | "all" | "unseen" | "unread" | "archived" (archived rows are hidden from "all").Default: all |
limit |
integer | Optional | Page size, clamped to [1, 100].Default: 50 |
cursor |
string | Optional | Opaque next-page cursor from a prior response. |
Response
| Field | Type | Description |
|---|---|---|
notifications |
object[] | Metadata-only rows (see below). |
cursor |
string | null | Next-page cursor, or null when the feed is exhausted. |
notification row
| Field | Type | Description |
|---|---|---|
notification_id |
string | Row id (used with the state endpoint). |
send_id |
string | The send this row belongs to. |
created_at |
integer | Row creation epoch (the sort-key timestamp component). |
title |
string | Display title. |
category |
string | Display category. |
type |
string | Display type. |
seen_at |
integer | null | When the recipient saw the notification. |
read_at |
integer | null | When the recipient read it. |
archived_at |
integer | null | When the row was archived. |
sparklink_code |
string | Opaque pointer the client opens to unseal the content. |
locked |
boolean | Always true: the content lives behind the SparkLink ("tap to view"). |
thread_id |
string | Present only for conversation threads. |
curl 'https://api.sparkvault.com/v1/products/notify/inbox?recipient_id=ing_019e66a4...&state=unread&limit=25' \
-H 'X-API-Key: sv_live_your_api_key'
Mark a Row's State
/products/notify/inbox/:notificationId/state
Apply a terminal display state to one row. Idempotent. A cross-tenant or missing row is reported as 404.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_id |
string | Required | The inbox owner. |
created_at |
integer | Required | The row's created_at (the sort-key timestamp), as returned by the inbox feed. |
state |
string | Required | "seen" | "read" | "archived". |
Response
| Field | Type | Description |
|---|---|---|
notification_id |
string | The row that was updated. |
state |
string | The state that was applied. |
updated_at |
integer | Update epoch. |
Send Status
The status endpoint is the sender's per-recipient delivery rollup for one send: “who has this reached, and how far did each recipient get?” It is metadata-only: it never returns the SparkLink pointer, the recipient's contact, or any policy internals.
/products/notify/sends/:sendId/status
Per-recipient delivery + read state for a send, plus aggregate counts. A send owned by another account is reported as 404.
Response
| Field | Type | Description |
|---|---|---|
send_id |
string | The send identifier. |
status |
string | The send-level status ("pending"). |
channels |
string[] | The resolved channel ladder stored on the send. |
created_at |
integer | Send creation epoch. |
title |
string | null | Display title. |
category |
string | null | Display category. |
type |
string | null | Display type. |
recipients |
object[] | Per recipient: { recipient_id, enqueued, seen_at, read_at, archived_at }. |
counts |
object | Aggregate rollup: { total, enqueued, seen, read, archived }. |
{
"data": {
"send_id": "ntsnd_01j9z4f6w8m3qk2c7d5h0abxyz",
"status": "pending",
"channels": ["email", "push"],
"created_at": 1719446400,
"title": "Your invoice is ready",
"category": "transactional",
"type": null,
"recipients": [
{
"recipient_id": "user@example.com",
"enqueued": true,
"seen_at": 1719446460,
"read_at": null,
"archived_at": null
}
],
"counts": { "total": 1, "enqueued": 1, "seen": 1, "read": 0, "archived": 0 }
},
"meta": { "api_version": "1.2.828", "request_id": "...", "response_ms": 34, "timestamp": 1719446400 }
}
Receipts
A receipt is a portable, JWKS-verifiable record of a verified recipient interacting with a SparkLink: opening
it (accessed), signing for it, approving or denying a bound action, or replying. Receipts are written to the
account-scope audit log in plaintext (they are designed to be presented as third-party proof without a vault
key), carrying the Identity-signed EdDSA token and the action_hash for bound interactions.
List Account Receipts
/products/notify/receipts
The account's verified-interaction receipts, newest first.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
integer | Optional | Page size, clamped to [1, 100].Default: 50 |
cursor |
string | Optional | Opaque next-page cursor from a prior response. |
interaction |
string | Optional | Narrow to one mode: "view" | "acknowledge" | "sign" | "approve" | "reply". |
Response
| Field | Type | Description |
|---|---|---|
receipts |
object[] | Receipt rows (see the receipt table below), newest first. |
cursor |
string | null | Next-page cursor, or null when the list is exhausted. |
Receipts for One Send
/products/notify/sends/:sendId/receipts
The signed receipts for one send, each attached to the recipient who produced it. Correlated by the per-grant SparkLink link_code (never the shared asset_id). A foreign send is reported as 404.
Response
| Field | Type | Description |
|---|---|---|
send_id |
string | The send the receipts belong to. |
receipts |
object[] | Receipt rows (see below), each carrying the recipient_id that produced it. |
receipt
| Field | Type | Description |
|---|---|---|
event_type |
string | The audit subtype: sparklink_accessed / signed / approved / denied / replied. |
occurred_at |
integer | Event epoch parsed from the audit sort key. |
interaction |
string | null | The interaction mode that produced the receipt. |
identity |
string | null | The verified identity that completed the ceremony. |
asset_id |
string | null | The sealed asset behind the grant (the Spark or ingot id). |
vault_id |
string | null | The vault holding the sealed asset, when applicable. |
link_code |
string | null | The masked per-grant SparkLink code used for correlation. |
link_type |
string | null | The SparkLink type that emitted the receipt. |
verification_level |
string | null | The level the recipient satisfied. |
action_hash |
string | null | The bound action hash for approve/sign interactions. |
signed_token |
string | null | The portable, JWKS-verifiable Identity-signed EdDSA proof. |
decision |
string | null | The approve/deny decision, when applicable. |
thread_id |
string | null | Present for conversation threads. |
reply_spark_id |
string | null | The sealed reply Spark, present on reply receipts. |
recipient_id |
string | Present on the per-send variant: the recipient who produced the receipt. |
Receipts are joined to a send by the per-grant SparkLink link_code (unique per recipient), not by
asset_id. An ingot's asset_id is shared across every send and share of that ingot,
so correlating on it would cross-attribute receipts from unrelated sends. The per-send correlation scan is
bounded (pages of 100 receipt rows, at most 20 pages), so on a very large receipt partition the result can
be bounded rather than authoritative-complete.
Company Configuration
An account's effective Notify behavior is the secure-by-default schema deep-merged with the account's stored overrides. The config drives channel resolution, escalation timing, retention, the categories recipients can mute, compliance, and branding.
Read Config
/products/notify/config
The account's Notify config as { effective, overrides }: defaults deep-merged with the stored override delta, so an editor can show what is customized vs. default.
Response
| Field | Type | Description |
|---|---|---|
effective |
object | The fully-resolved config (defaults merged with overrides). |
overrides |
object | The raw stored override delta for this account ({} when none). |
{
"effective": {
"delivery": {
"default_channels": ["in_app", "push", "web_push", "email", "sms"],
"channel_priority": {},
"realtime_fallback_delay": { "email_minutes": 2, "sms_minutes": 10, "voice_minutes": 30 },
"escalation_enabled": true,
"quiet_hours": { "enabled": false, "start": "22:00", "end": "07:00", "tz": "UTC" },
"coalesce_window_seconds": 30
},
"reliability": {
"dedup_window_seconds": 60,
"rate_limit_per_recipient_per_minute": 0,
"digest_frequency": "instant",
"dlq_threshold": 8
},
"history": { "history_ttl_days": 30, "max_rows_per_user": 2000, "receipt_retention_days": 365 },
"security": {
"default_confidentiality": "encrypted_reveal",
"verification_level": "none",
"reveal_freshness_minutes": 0,
"recall_window_hours": 24,
"reply_enabled": false,
"mandatory_seal_categories": []
},
"categories": {
"security": { "label": "Security", "description": "Sign-in, password, and account-safety alerts.", "required": true },
"account": { "label": "Account", "description": "Changes to your account, team, or plan.", "required": true },
"transactional": { "label": "Transactional", "description": "Receipts, confirmations, and status updates.", "required": false },
"product": { "label": "Product updates", "description": "New features, tips, and announcements.", "required": false },
"marketing": { "label": "Promotions", "description": "Offers and promotional messages.", "required": false }
},
"compliance": { "marketing_suppression": true, "signature_required": {} },
"branding": { "logo_url": null, "primary_color": null, "sender_name": null, "support_email": null, "locale": "en" }
},
"overrides": {}
}
Update Config
/products/notify/config
Validated partial PATCH of the company config, deep-merged onto existing overrides. Returns the new { effective, overrides }.
Patchable Sections
| Parameter | Type | Required | Description |
|---|---|---|---|
delivery |
object | Optional | default_channels, channel_priority, realtime_fallback_delay, escalation_enabled, quiet_hours, coalesce_window_seconds. |
reliability |
object | Optional | dedup_window_seconds, rate_limit_per_recipient_per_minute, dlq_threshold, digest_frequency. |
history |
object | Optional | history_ttl_days, max_rows_per_user, receipt_retention_days. |
security |
object | Optional | default_confidentiality, verification_level, reveal_freshness_minutes, recall_window_hours, reply_enabled, mandatory_seal_categories. |
categories |
object | Optional | Open-keyed map of { label, description, required }. Add your own topics (e.g. weekly_newsletter). The reserved "security" category is always required. |
compliance |
object | Optional | marketing_suppression, signature_required (per-category). |
branding |
object | Optional | logo_url, primary_color, sender_name, support_email, locale. |
curl -X PUT 'https://api.sparkvault.com/v1/products/notify/config' \
-H 'X-API-Key: sv_live_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"delivery": { "default_channels": ["push", "email"] },
"categories": {
"weekly_newsletter": { "label": "Weekly newsletter", "description": "Our weekly roundup.", "required": false }
}
}'
The PATCH rejects unknown top-level and nested keys, malformed values, and the owner-gated
channel_credentials (per-app channel secrets are SSM parameter references, never account-writable),
naming the offending field path, before any write. Only fields actually present are
checked; this is a partial PATCH, not a full replace.
Categories & Recipient Preferences
Two tiers decide delivery. The send resolves an offered channel ladder once; fan-out then narrows it per recipient from their stored preferences. Precedence is strict:
- Categories are the single source for what a send is classified as and what a recipient can mute. Each entry is
{ label, description, required };required: trueis the compliance floor and can never be muted.securityis platform-reserved and always required. - Recipient preferences are per person, per site (
{ global_off, muted_categories, channels }), managed on the recipient's auth.sv per-site page. A mandatory category is delivered regardless of any preference. - Application is fail-open: a preferences fault delivers as offered rather than dropping a notification. Preferences apply to recipients addressed by SVID (
ing_*); a suppressed recipient gets no row, no delivery, and is not metered.
Security & Billing
Security Properties
- Sealed by default: confidential content is sealed per recipient; transports carry only an opaque pointer.
- Single-use grants: each SparkLink burns on first use, so a leaked pointer cannot be replayed.
- Metadata-only reads: inbox, status, and receipts never echo plaintext or a recipient's raw contact.
- Tenant isolation: a cross-tenant send or notification is reported identically to one that never existed.
- Atomic cleanup: a partial or failed send hard-recalls every grant it minted: no orphaned sealed content.
- Provable interactions: receipts are portable, JWKS-verifiable EdDSA proofs that survive recall and expiry.
Billing
A notification is metered as one recipient × one logical notification: a fan-out to N channels still counts as one, idempotent on the send id. The composed Spark + SparkLink + Identity ceremony is included in the Notify unit (it is not double-charged on the standalone usage ledger). Metering is asynchronous and aggregated, with an advisory soft cap that warns and allows overage rather than hard-dropping mid-broadcast.
Error Handling
Errors return a stable shape. Validation errors name the offending field; tenant-isolation failures are deliberately indistinguishable from a missing resource.
Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR |
Missing/invalid field (e.g. empty recipients[], both content and ingot, an ingot send to >1 recipient, >500 recipients, or an unknown config key). |
| 401 | AUTHENTICATION_ERROR |
Missing or invalid account token (JWT or API key). |
| 404 | NOT_FOUND |
The send or notification does not exist OR belongs to another account (cross-tenant rows are reported as not-found). |
| 429 | RATE_LIMIT_EXCEEDED |
Too many requests. Retry after the indicated window. |
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Provide exactly one content source: content OR ingot",
"details": null
},
"meta": { "api_version": "1.2.828" }
}