Send and Call actions. This page describes the earlier V2 routing contract, which remains available for existing integrations.
Policies are optional. When no policy applies, a Relay uses the recipient DID’s ordinary service entries. A missing policy is normal fall-through behavior, not an error.
Policy scopes
OpenPayload stores at most one policy for each supported scope. JSON uses the exact enum valuesDID and PERSONA:
A tag is not a policy scope. Tags, labels, and categories mean the same thing in the policy model: an operator-defined message classification such as
Legal, Confidential, or Secret. The DDN envelope always uses the public top-level field tag, and a rule may match that value.
Address a Persona
Put the optional Persona after the DID or alias:@ or #. This makes the single @ separator unambiguous. The Directory resolves an alias to its DID before policy resolution.
Resolution order
The chain selects one winning rule from finalized state:- Validate and resolve the recipient DID.
- When the target contains a Persona, require that Persona to be active and registered.
- Evaluate rules in the Persona policy that match the recipient and tag.
- If no Persona rule matches, evaluate the DID policy.
- If no DID rule matches, return no policy and use the DID’s ordinary service entries.
priority wins. Ruleset order breaks an equal-priority tie. A winning Persona rule replaces DID policy routing; a route failure follows that rule’s own failure transitions and does not fall back to the DID policy.
Policy structure
Each policy is one versioned object with an orderedruleset:
operator_did must equal the scope DID. For a Persona scope, it must equal the active operator DID recorded on that Persona. The field identifies the controller whose signed authorization can replace or remove the policy; it is not a routing target.
Set revision to 1 when creating a scope’s first policy. A complete replacement uses the finalized revision plus one. The Directory returns 409 policy_revision_conflict instead of overwriting a concurrently updated policy.
policy_ttl_seconds bounds how long Relay and Cache nodes can reuse a resolved policy lookup. It accepts 1 through 300 seconds and defaults to 60 when omitted from a new policy request. It does not control message retention; requested_cache_seconds controls that separately. Existing policies receive a 60-second lookup TTL during the chain storage upgrade.
policy_id is a deterministic 32-byte identifier represented as 0x followed by 64 hexadecimal characters:
0 for DID and 1 for PERSONA. Initial condition shapes are:
All conditions in a rule must match. An empty
conditions array matches every message in that policy scope. Tags are nonblank, case-sensitive, operator-defined UTF-8 strings of at most 96 bytes; control characters and NUL are prohibited. Leading or trailing spaces are preserved rather than stripped, so operators should avoid spellings that are easy to confuse.
Delivery plans
DeliveryPlan/v1 is a bounded directed graph:
Forwardsends an envelope to a named next-hop service.Storeselects named Cache services and a replica placement requirement.Archiveroutes an envelope to a compatible archive service without changing the network TTL.- A transition uses
triggerto select the next step afterSuccess,Failure, orAlways. Nextadvances one branch. Multiple transitions with the samefromandtrigger, differenttostep IDs, andmode: "Fork"start parallel branches.
Success and Failure may each have at most one Next transition. An Always Next transition is exclusive with every other Next transition from that source. Use Fork transitions for fan-out; the Directory and chain reject ambiguous Next combinations.
Each target contains service_did, service_id, priority, and weight. It cannot contain an arbitrary URL. The DID and service ID must resolve to the exact compatible public service type: Forward uses OpenPayloadRelayService, Store uses OpenPayloadCacheService, and Archive uses OpenPayloadArchiveService. An inspection DID participates as a next hop by publishing a Relay service. One physical endpoint can publish distinct Cache and Archive service entries when it supports both roles. priority defines failover tiers. Priority selection orders greater priorities first and uses weight as a stable tie-breaker. Weighted Store selection uses the message ID to produce deterministic weighted ordering within one priority tier; it never moves a lower-priority target ahead of a higher-priority tier. Forward and Archive use priority ordering because they do not declare placement.
Every endpoint published by a service referenced from a policy must be a public HTTPS base URI. Credentials, query strings, and fragments are prohibited. The Directory rejects localhost names, .localhost, .local, .internal, and .home.arpa names, plus known private, loopback, link-local, multicast, carrier-grade NAT, documentation, and other special-use IP literals. A noncompliant endpoint is not eligible for policy delivery. This check validates the published URI and literal host; it does not pin future DNS answers or guarantee protection from DNS rebinding.
A plan can contain at most one Forward step. Its successor steps run afterward under the same pinned policy revision; a policy cannot create a multi-hop chain of Forward steps.
The finalized winning policy remains fixed for one delivery attempt. If a plan forwards once and then continues with successor steps, those steps use the same selected revision even when chain state changes during delivery. Compatible clients submit only the original envelope and must not add policy context or provenance. Network-managed provenance is outside the public client contract.
The policy action can also reject delivery or select the ordinary DID services explicitly:
Reject/v1.reason_code is a public unsigned integer from 0 through 65535. The first protocol version requires an acyclic graph. Clients and nodes must reject a plan containing an unknown action or condition instead of attempting to interpret it as an operator command.
BaseDidDelivery/v1 applies the recipient DID’s ordinary service routing once. If that routing selects another Relay, the receiving Relay continues with the recipient’s non-Relay delivery options instead of forwarding through another Relay again.
Policy V2 bounds each policy to 32 rules, each rule to 8 conditions, and each delivery plan to 32 steps and 64 transitions. A step can name at most 16 distinct service targets. Rule and step IDs, including the from and to references in transitions, use 1 through 64 ASCII letters, digits, ., _, :, or -. Priorities range from 0 through 65535; weights range from 1 through 65535.
A policy also has a maximum validation budget of 4096 units. Count one unit for each rule, condition, DID in a RecipientIn condition, delivery-plan step, route target, and transition. The Directory and chain reject a policy whose total exceeds 4096 even when every individual collection is within its own bound.
A Store step requires placement. desired_replicas is from 1 through 4 and cannot exceed the number of targets; required_replicas is from 1 through the desired count. selection is Priority or Weighted. Forward and Archive steps cannot declare Cache placement.
Constraints and network ceilings
Message constraints belong to the DID or Persona record. A rule may only narrow the effective base constraints. It cannot increase an effective scope limit or a network ceiling. For each field, the resolver takes the minimum of the network ceiling, the recipient DID’s configured constraint when present, and the addressed Persona’s constraint when present. Persona constraints still apply when no Persona rule matches and resolution falls through to a DID rule. The winning rule’s optional overrides are then minimum-clamped against that merged base; an override can never cause a Persona winner to lose precedence.
A scope can publish
requested_cache_seconds, max_http_envelope_bytes, max_unchunked_message_bytes, max_chunk_bytes, and max_replicas. The chain preserves the positive requested_cache_seconds value and derives the read-only effective_ttl_seconds:
requested_cache_seconds limits how long a Cache may retain a message. It does not control how long a Relay or Cache retains a policy lookup.
max_chunk_bytes cannot exceed max_unchunked_message_bytes, and the unchunked limit cannot exceed the raw HTTP-envelope limit. A rule can omit overrides it does not need.
max_message_bytes and max_chunks remain present in the Policy V2 SCALE shape only for backward wire compatibility. Current runtime responses normalize them to 4294967295 and 65535, respectively. They are not operator policy controls, Relay admission limits, or Cache admission limits; clients should omit them from JSON requests. Nodes protect themselves with per-envelope and per-chunk limits plus bounded staging capacity.
When a sender supplies message_group_id, sequence_number, and total_chunks, a Relay stages the opaque envelopes until all declared sequence numbers are present. It does not inspect or reassemble their payloads. The staging manifest and completed-delivery progress are durable across a normal process restart. If the set does not complete within the Relay’s staging window, the Relay purges that incomplete set. A client may retry an identical sequence; conflicting content for the same sequence is rejected.
The Relay derives the message TTL as min(envelope requested ttl, resolved effective ttl) and computes one absolute expires_at. Every stored copy uses that same hard upper bound, and forwarding or replication cannot extend it. A Cache can purge a copy earlier after acknowledgement or when current public authorization or placement no longer permits retention.
Replicas and acknowledgement
AStore step can name eligible Cache services and request multiple replicas. desired_replicas is the target count; required_replicas is the minimum that must accept the envelope for the step to succeed.
details.accepted_caches in a Relay result reports Cache endpoints that accepted a Store step, while details.accepted_archives reports endpoints that accepted an Archive step. Both lists are limited to storage observed by the responding Relay; an upstream Relay does not aggregate later accepts after a Forward step. These lists are not embedded in the stored envelope for the recipient. After safely persisting or processing a retrieved Cache copy, the recipient acknowledges it at the Cache that returned it. A successful acknowledgement from one Cache does not delete another Cache’s replica.
Public policy metadata
Policy scopes, rule conditions, service references, priorities, and graph structure are public routing metadata. Do not put plaintext message contents, secrets, private keys, or confidential business logic in a policy or public tag.Removed legacy model
The current public contract replaces the earlier recipient/tag/Persona policy maps and DID policy-link endpoints:- Recipient policies are now DID-scoped policies at
/delivery-policies/dids/{did}. - Persona policies use registered DNS Personas rather than Persona DIDs.
- Tag-scoped policy endpoints are removed; tags are rule conditions.
effective_ttlin blocks is replaced by requested and effective clock-time seconds.- Pricing, release-fee,
cache_eligible, and policy lifecycle fields are removed. - DID delivery-policy-link endpoints are removed because a scope owns its single policy directly.
Current Directory servers do not register the removed routes; they return
404.
