← Back to Argo

Agent Information Requests

Last updated: September 27, 2026

Any agent with an Ethereum key can send an Argo user a short list of questions. The user sees the request in the Argo app, drafts answers from their own journal if they choose, edits them, and sends them back. Replies arrive at your webhook, signed by Argo. No API key or registration is needed.

How it works

  1. Your agent signs a request with its wallet key and POSTs it to /v1/inbox/requests.
  2. Argo verifies the signature (and your ENS name, if you send one) and places the request in the recipient's inbox.
  3. The recipient answers, declines individual questions, or ignores the request. Nothing is sent without their review.
  4. If they answer, Argo POSTs a signed reply to your webhookUrl. Requests expire after 30 days.

Addressing a user

FormExampleNotes
@username@konradCase-insensitive. The @ is optional. Users choose a username in the app under Settings.
0x…0xabcd…1234Matches the user's Argo Soul wallet or their linked sign-in wallet.

There is no directory or search endpoint. Get the handle from the user.

Send a request

POST https://api.luminalog.com/v1/inbox/requests
Content-Type: application/json
FieldTypeLimits
tostring1–64 chars: @username, username, or 0x address
from.addressstring0x + 40 hex; must be the signing address
from.ensstring, optional3–255 chars; must resolve to from.address on Ethereum mainnet
from.namestring1–80 chars, shown to the user
from.descriptionstring1–500 chars, shown to the user
reasonstring1–1000 chars, shown to the user
questionsstring[]1–10 questions, 1–500 chars each
webhookUrlstringPublic https URL, up to 2048 chars; no credentials, localhost, or private IPs
issuedAtstringISO-8601 with timezone, within 10 minutes of server time
noncestring16–128 chars of A–Z a–z 0–9 _ -
signaturestring0x-prefixed EIP-191 signature (below)

Success returns 201 with { "id", "status": "pending", "expiresAt" }. The id is the request hash, and it comes back as requestId in the reply.

Sign it

Hash a JSON array of the fields in this exact order, then sign "Argo information request v1\n" + sha256hex with personal_sign (EIP-191). A positional array means there is no key ordering to get wrong. Hash the strings exactly as you send them, with no trimming.

[to, from.address.toLowerCase(), from.ens ?? "", from.name, from.description,
 reason, questions, webhookUrl, issuedAt, nonce]

JavaScript (viem)

import { createHash, randomBytes } from 'node:crypto'
import { privateKeyToAccount } from 'viem/accounts'

const account = privateKeyToAccount(process.env.AGENT_KEY)
const body = {
  to: '@konrad',
  from: { address: account.address, name: 'Match Agent', description: 'Matches founders with co-founders.' },
  reason: 'You look like a strong co-founder fit.',
  questions: ['What are you building?', 'What skills are you looking for?'],
  webhookUrl: 'https://agent.example.com/replies',
  issuedAt: new Date().toISOString(),
  nonce: randomBytes(16).toString('hex'),
}
const payload = JSON.stringify([
  body.to, body.from.address.toLowerCase(), body.from.ens ?? '', body.from.name,
  body.from.description, body.reason, body.questions, body.webhookUrl, body.issuedAt, body.nonce,
])
const hash = createHash('sha256').update(payload, 'utf8').digest('hex')
body.signature = await account.signMessage({ message: 'Argo information request v1\n' + hash })

const res = await fetch('https://api.luminalog.com/v1/inbox/requests', {
  method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
})

Python (eth_account)

import hashlib, json
from eth_account import Account
from eth_account.messages import encode_defunct

payload = json.dumps([
    body["to"], body["from"]["address"].lower(), body["from"].get("ens", ""),
    body["from"]["name"], body["from"]["description"], body["reason"],
    body["questions"], body["webhookUrl"], body["issuedAt"], body["nonce"],
], separators=(",", ":"), ensure_ascii=False)
digest = hashlib.sha256(payload.encode("utf-8")).hexdigest()
signed = Account.sign_message(encode_defunct(text="Argo information request v1\n" + digest), private_key=KEY)
body["signature"] = "0x" + bytes(signed.signature).hex()  # the 0x prefix is required

Errors

StatuserrorMeaning
400invalid_bodyA field is missing, the wrong type, or out of limits
400invalid_webhookNot https, has credentials, or points at a private or local address
400stale_requestissuedAt is more than 10 minutes from server time
401bad_signatureMalformed, or does not recover to from.address
422ens_mismatchfrom.ens does not resolve to from.address
503ens_unavailableENS lookup failed; retry later
404recipient_not_foundNo Argo user matches to
409duplicateThis exact signed request was already received
429rate_limitedOver 30 requests an hour from your IP, or 3 a day from your address to one user
429inbox_fullThe user has 50 pending requests

Receive the reply

Argo POSTs this body to your webhookUrl. There is one entry per question, in the original order, and a declined question has answer: null.

{
  "type": "argo.info-response.v1",
  "requestId": "<request hash>",
  "respondent": { "username": "konrad", "wallet": "0x…" },
  "answers": [
    { "question": "What are you building?", "answer": "A private AI journal.", "declined": false },
    { "question": "What skills are you looking for?", "answer": null, "declined": true }
  ],
  "respondedAt": "2026-09-27T10:05:00Z"
}

Headers: X-Argo-Signer (address) and X-Argo-Signature, an EIP-191 signature over "Argo information response v1\n" + sha256hex(raw body bytes). Return any 2xx. Argo retries network errors, 429, and 5xx up to 3 times. It does not retry other 4xx responses and does not follow redirects.

Verify the reply

Fetch Argo's signing address once from GET https://api.luminalog.com/v1/inbox/signer and pin it. Check every reply against that pinned address, not against the X-Argo-Signer header, because anyone can set a header. Hash the raw bytes you received, before any JSON parsing.

import hashlib
from eth_account import Account
from eth_account.messages import encode_defunct

ARGO_SIGNER = "0x…"  # from GET /v1/inbox/signer, fetched once and pinned

def is_genuine(raw_body: bytes, signature: str) -> bool:
    digest = hashlib.sha256(raw_body).hexdigest()
    msg = encode_defunct(text="Argo information response v1\n" + digest)
    return Account.recover_message(msg, signature=signature).lower() == ARGO_SIGNER.lower()

Things to expect

  • Replies may never come. The user can ignore a request, and unanswered requests expire after 30 days.
  • Answers are written by a person. They may be drafted from the user's journal, but the user reviews and edits them before sending.
  • Deduplicate on requestId. A reply can arrive more than once in rare failure cases.
  • Only your questions are stored. Argo keeps the request until it is answered, ignored, or expired. Answers pass through to your webhook and are not stored on Argo's server.