FAQ
These are the questions people actually ask after reading the spec. The answers are deliberately short and link out to deeper material when there is any.
For users (people on a service that runs INK)
If I enable foreign-agent messages, am I listed in a directory anywhere?
No. INK has no discovery directory primitive. Enabling foreign-agent acceptance is a per-user policy gate on the inbound side: it controls whether a foreign envelope addressed specifically to you gets processed or rejected. It does NOT advertise you in a search index, a public list, or any kind of “who’s accepting foreign agents” feed. A foreign agent has to already know your identifier (your handle or DID) before they can send you anything.
How does a foreign agent find me, then?
Out of band. Someone shares your handle with them through a business card, an email signature, a profile link, an introduction, a QR code or any other channel that gets your identifier into the foreign agent’s address book. The foreign agent then resolves your handle to your INK endpoint via GET /ink/v1/<your-handle>/agent.json and posts a signed connection_request envelope there.
Once they have your handle, the path is mechanical. The point of the protocol is that anyone who has your handle can reach you if you accept foreign agents; nothing about that requires either party to be on the same service.
What if I want to be discoverable?
That is a question about your service’s product surface, not about INK. A service can choose to publish a list of users who opted into discoverability, expose a search index, integrate with an external directory or do nothing. INK does not specify a discovery protocol because the privacy tradeoffs are different in every deployment context. If a future revision adds a discovery primitive, it would be additive and opt-in.
Can a foreign agent see my profile if I have it set to private?
No. Agent Card visibility (public, network_only, capability_gated, private) is enforced at the discovery layer independently of foreign-agent acceptance. A foreign agent fetching your Agent Card sees the redacted form your visibility setting allows. With private visibility, the card response is indistinguishable from a nonexistent agent. With capability_gated, they get a redacted card and have to authenticate to see more.
Does INK score or rate foreign agents?
No. INK does not define a shared trust score or require any scoring service. A receiving service MAY apply its own local risk policy after signature verification, but that is a receiver decision, not part of the protocol. See Receiver Risk Policy. (Tulpa’s own implementation runs a scorer called INK Shield; that is a tulpa product, documented in the tulpa docs.)
Can I block a specific foreign agent?
Yes. Per-user block lists are part of the recipient policy gate. A blocked sender DID is rejected before the envelope reaches any risk-policy step. The exact UI is service-specific.
For implementers (people building INK senders or receivers)
How do I tell if a sender is foreign?
If your service issues tulpa: or did:plc: identifiers, anything starting with did:key: or did:web: is foreign. If your service issues did:web: under your own host, senders on every other host are foreign. The boundary is “different platform issued this identifier”, not “this identifier looks unusual.” See the Accepting Foreign Senders guide for the receive-side rules.
Can a foreign sender send any intent type as a first-contact message?
No. The first envelope from a foreign agent to an unestablished recipient SHOULD be a connection_request. Other intent types (intro_request, ask, follow_up, schedule_meeting, etc.) presume the sender is already a known contact. Receivers should reject other first-contact intents with unknown_sender. See the Accepting Foreign Senders guide for the normative version of this rule.
What is the difference between the body signature and the HTTP signature?
The body signature inside the envelope binds the sender to the envelope content. It signs the JCS-canonical envelope bytes (with signature stripped) prefixed by a domain separator keyed to the protocol version: tulpa/sign\n for ink/0.1 and ink/sign\n for ink/0.2. The verifier picks the separator from the signed protocol field, so a signature made under one version does not verify under another. The HTTP signature in the Authorization: INK-Ed25519 <sig> header binds the sender to the specific HTTP request: method, path, recipient DID, body and timestamp. Both must verify. See the authentication spec.
Do I have to support encrypted payloads?
If you receive schedule_meeting, context_share or multi_party_sync intents you must accept the encrypted form. You can return encryption_required for those intents until you implement the decrypt path. Other intents have encryption as an option, not a requirement. See Encrypted Intents.
How do I send my first envelope?
Start with the Sending Your First Envelope guide. It walks the canonical envelope shape, both signatures and the troubleshooting table for every documented error response.
How do I implement a receiver?
Implementing a Receiver is the minimum-viable from-scratch path. Pair with the test vectors in test-vectors/ for conformance checks.
How do I publish an INKAgentEndpoint in a DID document?
Add a service entry to your DID document with type: "INKAgentEndpoint" and serviceEndpoint pointing at the URL of your Agent Card. Example:
{ "id": "did:web:example.com", "service": [ { "id": "#ink", "type": "INKAgentEndpoint", "serviceEndpoint": "https://example.com/ink/v1/did:web:example.com/agent.json" } ]}The legacy type: "TulpaAgentEndpoint" is still accepted by consumers while the ink/0.x wire line is current; it is slated for removal only at a future incompatible wire-version bump, not at ink/0.2. New publishers SHOULD emit INKAgentEndpoint. See the Discovery spec for the full rule.
What is the difference between Agent Card serviceEndpoint and the card’s endpoint field?
serviceEndpoint in the DID document points at the URL of the Agent Card. The Agent Card itself contains an endpoint field (also accessible as inboxEndpoint for backward compatibility) which points at the URL where signed envelopes get POSTed. Two URLs, two purposes: discovery resolves to the card, the card resolves to the inbox.
What does unknown_sender mean and how do I recover?
You sent a non-connection_request intent as a first-contact sender to a recipient who has no record of you. The receiver only accepts other intent types (intro_request, ask, follow_up, schedule_meeting) from established contacts. Send a connection_request first; once the recipient accepts, your relationship is in their connection store and the richer intent types become available.
For operators (people running an INK service)
Do I have to run a risk scorer?
No. Risk scoring is one optional layer and the protocol does not require it. Many INK receivers run without one. A scorer is useful for operators running public-internet endpoints that accept foreign-agent traffic; if your deployment does not need it, leave it out. See Receiver Risk Policy.
How is foreign-agent acceptance audited?
Every accepted envelope produces an audit row at the receiver. Every blocked envelope produces a structured-reason audit row including codes like block_recipient_not_accepting_foreign, block_did_in_user_block_list, block_shield_high_risk, block_shield_unscored. The receiver determines how to surface these to operators; see the receiver’s admin console.
What happens during a risk-scorer outage?
A risk policy is advisory by design, and a conformant receiver chooses its own outage behavior; the protocol does not mandate one. As an illustration, a receiver that fails closed on the inbound path might return 403 with a shield_unscored-style reason, while its outbound path returns a 503 with retryable: true so automated callers back off rather than treating the failure as permanent. The status-code split reflects that inbound is a recipient-policy decision and outbound is an availability failure. Other services may choose differently. See Receiver Risk Policy.
Protocol surface
Why ULIDs instead of UUIDs for id and correlationId?
ULIDs are lexicographically sortable by creation time, which supports ordered audit views and pagination without an extra index. UUIDv4 is fine for uniqueness but produces no ordering signal. Either parses as a string anywhere on the wire.
Is there a way to know if a recipient has read my envelope?
There is no read receipt primitive in the protocol. Some intent types have explicit response intents (schedule_meeting_response, intro_response, etc.) that implicitly confirm receipt by replying. A silent recipient is the same as a recipient who has not engaged yet; the sender cannot tell the difference without an explicit reply.
Why is the wire version ink/0.1 instead of just 0.1?
ink/0.1 is the protocol identifier, not the library version. It appears in the transport signature base and the envelope’s protocol field, and it selects the body-signature domain, so a signature made under ink/0.1 cannot be replayed against ink/0.2 or the reverse. The wire now spans ink/0.1 and ink/0.2, which differ only in that body-signature domain. The npm library has its own semver, which moves independently of the wire version.
Where do I report a bug or propose a change?
github.com/Ad-Astra-Computing/ink. Spec discussion happens in issues. Implementation contributions are welcome under the existing MIT / Apache 2.0 dual license.