Skip to content

Host API

The DIPLOMATIC protocol has 4 endpoints, plus the WebSocket channel.

Endpoints

  • USER Registers a user.
  • PEEK Fetches bag headers uploaded since provided sequence number SEQ.
  • PUSH Uploads bags.
  • PULL Fetches a list of bags with provided sequence numbers.
  • NOTF https://hostURL?t=<authTSHex> Initiate a WebSocket connection to receive new bags in real-time.

Response Header

All API responses (excluding the websocket) begin with a standard response header. It looks like this:

export interface IRespHead {
  // Overall status of the request.
  status: Status; // 1 byte

  // NTP-style timestamps for client to compute offset from host clock.
  timeRcvd: Date; // ~6 bytes
  timeSent: Date; // ~6 bytes

  subscription: ISubscriptionMetadata; // ~30 bytes
}

Each response includes a status code so the client can know if the request succeeded or if not, why not. The timeRcvd and timeSent timestamps allow the client to determine if it is in-sync with the host's clock, using NTP logic, which can indicate if the client's clock is wrong. The subscription information allows the client to anticipate if it is at risk of losing service from this host, which could compromise data availability, particularly if the client is not maintaining a full message archive.

// ~30 bytes.
export interface ISubscriptionMetadata {
  // Duration of subscrption term in milliseconds.
  // 0 indicates an indefinite term (either lifetime or pay-as-you-go).
  term: number;

  // Milliseconds since start of term.
  elapsed: number;

  // "static" usage, i.e. storage.
  stat: IUsageQuota;

  // "dynamic" usage, i.e. time (bandwidth, CPU time, ...).
  dyn: IUsageQuota;
}

export interface IUsageQuota {
  quota: number;
  usage?: number;
}

Total size of the response header is about 43 bytes.

Host storage interface

Wire endpoints are thin over a host-local storage abstraction (IStorage). Implementations (SQLite, D1, in-memory) provide:

MethodRole
addUser / hasUserRegistration
subMetaSubscription / quota metadata for response headers
setBags(pubKey, bags)Persist bags; return host seq for each bag in order
getBodies(pubKey, seqs)Ciphertext bodies for PULL; missing seqs omitted
listHeads(pubKey, minSeq)Heads with seq > minSeq for PEEK

setBags

ts
setBags(pubKey: PublicKey, bags: IBag[]): Promise<ValStat<number[]>>
  • Empty list succeeds and returns [].
  • Return value is one host sequence number per input bag, same order as bags.
  • Atomic seq assignment: a single call must allocate contiguous seqs without racing another concurrent setBags for the same user (e.g. one SQL transaction, or D1 batch with MAX(seq)+1 inside each INSERT … RETURNING seq).
  • A single bag is just setBags(pubKey, [bag]) — there is no separate one-shot store API.

PUSH verifies signatures, then stores all valid bags with one setBags call (not one write per bag).

getBodies

ts
getBodies(pubKey: PublicKey, seqs: number[]): Promise<ValStat<{ seq: number; bodyCph: Uint8Array }[]>>
  • Empty seqs succeeds and returns [].
  • Missing seqs are omitted from the result (not an error); only found bags are returned.
  • Implementations should use a single query (or few chunked IN (...) queries), not one round-trip per seq.

PULL calls getBodies once per request with the full seq list.