a2ally™ Sign up

Developers · SDK

Your first receipt in 10 minutes

One script per language, standard library only, three calls: open a free account, seal a real receipt for an action that actually happened, and read its public ledger entry back. The scripts are the ones in the repository (sdks/typescript/first-receipt.ts · sdks/python/first_receipt.py), printed from the same templates this page and GET /v1/sdk/first-receipt serve — they cannot drift from the API shape the test suite runs them against.

The three calls

  1. Open a free account (or reuse your key) — POST /v1/accounts/signup · none — body carries accept_terms: true, given explicitly by the runner · returns api_key (shown once) and the account record
  2. Seal your first receipt — POST /v1/me/activity-receipts · Authorization: Bearer <api_key>, plus an Idempotency-Key naming the real event · returns receipt.id, receipt.ledger_seq and receipt.commitment
  3. Read the public ledger entry back — GET /v1/ledger/entries/{ledger_seq} · none — verification is free forever · returns the entry and its status (pending until its block seals, then the signed inclusion proof)

TypeScript — Node 18+, no dependencies

Save as first-receipt.ts and run: node first-receipt.ts --accept-terms # or: A2ALLY_API_KEY=<your key> node first-receipt.ts

// a2ally first receipt — TypeScript quickstart (Node 18+, no dependencies).
// Three calls: open a free account (or reuse A2ALLY_API_KEY), seal one real
// activity receipt, and read its public ledger entry back. Under ten minutes.
// Terms: https://www.a2ally.com/terms — the script opens an account ONLY when you pass
// --accept-terms yourself. Keep your key out of prompts, repos and shared logs.
// An optional http(s) argument overrides the base URL (a self-hosted deployment).
const base = (process.argv.find((a) => a.startsWith("http://") || a.startsWith("https://")) || "https://www.a2ally.com").replace(/\/+$/, "");

async function call(method, path, opts) {
  const o = opts || {};
  const res = await fetch(base + path, {
    method,
    headers: {
      accept: "application/json",
      ...(o.body !== undefined ? { "content-type": "application/json" } : {}),
      ...(o.key ? { authorization: "Bearer " + o.key } : {}),
      ...(o.idempotencyKey ? { "idempotency-key": o.idempotencyKey } : {}),
    },
    body: o.body !== undefined ? JSON.stringify(o.body) : undefined,
  });
  const text = await res.text();
  let data = null;
  try { data = text ? JSON.parse(text) : null; } catch { data = null; }
  if (!res.ok) throw new Error(method + " " + path + " failed (HTTP " + res.status + "): " + text.slice(0, 300));
  return data;
}

async function main() {
  let key = process.env.A2ALLY_API_KEY || "";
  if (!key) {
    if (!process.argv.includes("--accept-terms")) {
      throw new Error("No A2ALLY_API_KEY set. Set it, or re-run with --accept-terms to open a free account (terms: " + base + "/terms).");
    }
    const su = await call("POST", "/v1/accounts/signup", { body: { display_name: "SDK first receipt", accept_terms: true } });
    key = su.api_key;
    if (!key) throw new Error("Signup answered without an api_key.");
    console.log("Account opened. Save this key now — it is shown once: " + key);
  }
  const eventId = "sdk-first-" + globalThis.crypto.randomUUID();
  const recorded = await call("POST", "/v1/me/activity-receipts", {
    key, idempotencyKey: eventId,
    body: { action_type: "sdk.first_receipt", summary: "First receipt sealed from the a2ally SDK quickstart." },
  });
  const receipt = recorded.receipt;
  console.log("Receipt sealed: " + receipt.id);
  console.log("Ledger sequence: " + receipt.ledger_seq + " · commitment: " + receipt.commitment);
  const opened = await call("GET", "/v1/me/activity-receipts/" + receipt.id, { key });
  const entryPath = opened.receipt.ledger_entry_url;
  const proof = await call("GET", entryPath, {});
  console.log("Public ledger entry status: " + proof.status + " — re-check any time, no key, at " + base + entryPath);
}

main().catch((e) => { console.error(e.message); process.exit(1); });

Python — 3.9+, standard library only

Save as first_receipt.py and run: python3 first_receipt.py --accept-terms # or: A2ALLY_API_KEY=<your key> python3 first_receipt.py

# a2ally first receipt — Python quickstart (Python 3.9+, standard library only).
# Three calls: open a free account (or reuse A2ALLY_API_KEY), seal one real
# activity receipt, and read its public ledger entry back. Under ten minutes.
# Terms: https://www.a2ally.com/terms — the script opens an account ONLY when you pass
# --accept-terms yourself. Keep your key out of prompts, repos and shared logs.
# An optional http(s) argument overrides the base URL (a self-hosted deployment).
import json
import os
import sys
import urllib.request
import uuid

_arg_base = [a for a in sys.argv[1:] if a.startswith("http://") or a.startswith("https://")]
BASE = (_arg_base[0] if _arg_base else "https://www.a2ally.com").rstrip("/")


def call(method, path, key=None, body=None, idempotency_key=None):
    data = json.dumps(body).encode() if body is not None else None
    headers = {"accept": "application/json"}
    if body is not None:
        headers["content-type"] = "application/json"
    if key:
        headers["authorization"] = "Bearer " + key
    if idempotency_key:
        headers["idempotency-key"] = idempotency_key
    req = urllib.request.Request(BASE + path, data=data, method=method, headers=headers)
    try:
        with urllib.request.urlopen(req) as res:
            return json.load(res)
    except urllib.error.HTTPError as e:
        raise SystemExit(method + " " + path + " failed (HTTP " + str(e.code) + "): " + e.read().decode()[:300])


def main():
    key = os.environ.get("A2ALLY_API_KEY") or ""
    if not key:
        if "--accept-terms" not in sys.argv:
            raise SystemExit("No A2ALLY_API_KEY set. Set it, or re-run with --accept-terms to open a free account (terms: " + BASE + "/terms).")
        su = call("POST", "/v1/accounts/signup", body={"display_name": "SDK first receipt", "accept_terms": True})
        key = su.get("api_key") or ""
        if not key:
            raise SystemExit("Signup answered without an api_key.")
        print("Account opened. Save this key now — it is shown once: " + key)
    event_id = "sdk-first-" + str(uuid.uuid4())
    recorded = call("POST", "/v1/me/activity-receipts", key=key, idempotency_key=event_id,
                    body={"action_type": "sdk.first_receipt", "summary": "First receipt sealed from the a2ally SDK quickstart."})
    receipt = recorded["receipt"]
    print("Receipt sealed: " + receipt["id"])
    print("Ledger sequence: " + str(receipt["ledger_seq"]) + " · commitment: " + str(receipt["commitment"]))
    opened = call("GET", "/v1/me/activity-receipts/" + receipt["id"], key=key)
    entry_path = opened["receipt"]["ledger_entry_url"]
    proof = call("GET", entry_path)
    print("Public ledger entry status: " + str(proof["status"]) + " — re-check any time, no key, at " + BASE + entry_path)


if __name__ == "__main__":
    main()

Honest limits

Three calls, under ten minutes, standard library only: (1) open a free account — only with your explicit --accept-terms, or reuse the key in A2ALLY_API_KEY; (2) seal one activity receipt for an action that actually happened (POST /v1/me/activity-receipts, with an Idempotency-Key so a retry returns the same receipt); (3) read the receipt's public ledger entry back — verification is free and needs no key. Only the action type, the time and a commitment are public; the summary stays in the private opening, readable only with your key. Keep your key out of prompts, repositories and shared logs.

A receipt records what your account submitted; it does not by itself prove the action happened — the evidence story is at /record-actions. The full SDKs (offline verification, activity recording, operations) live in the repo under sdks/; the REST core quickstarts are Python and Go; every client connects at /quickstart.

For agentsQuickstartRecord actionsPricing