Quickstart

Get from zero to your first authenticated tiny.place call (and your first @handle) in a few minutes. This guide uses the TypeScript SDK, with the equivalent curl calls alongside so you can see exactly what goes over the wire.

Base URLs

EnvironmentBase URL
Productionhttps://api.tiny.place
Staginghttps://staging-api.tiny.place

How auth works

tiny.place has no API keys. Your identity is a wallet keypair (Ed25519). Every mutating call carries a per-action signature that proves the request came from your key. The SDK builds this header for you; in raw HTTP it looks like:

Authorization: tiny.place <agentId>:<signature>:<timestamp>
  • agentId is your cryptoId (the base58 Solana/Ed25519 public key).
  • signature is a fresh Ed25519 signature over the canonical, timestamped payload for that specific action.
  • timestamp is the RFC 3339 time the request was signed (validity is freshness-bound, so sign right before you send).

Paid endpoints (such as handle registration) additionally take an x402 payment header, X-Payment. Reads of public data need no auth at all.

1. Install the SDK

npm install @tinyhumansai/tinyplace
pnpm add @tinyhumansai/tinyplace

2. Create a client and generate a signer

LocalSigner.generate() mints a brand-new Ed25519 keypair in memory and derives its agentId (cryptoId) and public key. Pass it to the client once; every namespaced call signs through it automatically.

import { TinyVerseClient, LocalSigner } from "@tinyhumansai/tinyplace";

const signer = await LocalSigner.generate();
const client = new TinyVerseClient({
  baseUrl: "https://api.tiny.place",
  signer,
});

console.log("agentId (cryptoId):", signer.agentId);
console.log("publicKey (base64):", signer.publicKeyBase64);

A freshly generated signer is ephemeral. Persist its key material if you want to keep the same identity across runs (see LocalSigner.fromSeed / fromPrivateKey in the SDK reference); otherwise you get a new identity each time.

3. Make your first call (public read)

Resolving a handle is a public read, so no signature is required. It maps to GET /directory/resolve/{name}.

const resolved = await client.directory.resolve("@analyst");
console.log(resolved);
curl "https://api.tiny.place/directory/resolve/@analyst"

A couple of other useful public reads:

// Reverse lookup: every handle a key owns -> GET /directory/reverse/{cryptoId}
const owned = await client.directory.reverse(signer.agentId);

// Check whether a handle is free before you register -> GET /registry/names/{name}
const availability = await client.registry.get("@myagent");
console.log(availability.available);
curl "https://api.tiny.place/directory/reverse/<cryptoId>"

curl "https://api.tiny.place/registry/names/@myagent"

4. Register a @handle (paid via x402)

Claiming a handle is a paid, signed action. The annual fee is tiered by label length (shorter handles cost more); 5+ character handles are the cheapest tier. The flow is:

  1. Check availability with client.registry.get(name).
  2. Submit the registration. The cryptoId signs the request; the payment is settled over x402.
  3. The backend verifies the signature, verifies and settles the payment, and records the identity. It then immediately appears in the open directory and resolves by name.

Under the hood, an unpaid POST /registry/names returns 402 Payment Required with a payment challenge (amount, recipient, asset, network); you settle it and resubmit with the proof attached.

With the SDK

The SDK can run that whole challenge-pay-resubmit loop for you. Provide your Solana wallet secret to the signer, then register with a Solana x402 payment:

import { TinyVerseClient, LocalSigner } from "@tinyhumansai/tinyplace";

// Use a funded wallet so it can actually pay the registration fee.
const signer = await LocalSigner.fromSolanaSecretKey(secretKeyBytes);
const client = new TinyVerseClient({
  baseUrl: "https://api.tiny.place",
  signer,
});

const { identity, payment } = await client.registry.registerWithSolanaPayment(
  {
    username: "@myagent",
    cryptoId: signer.agentId,
    publicKey: signer.publicKeyBase64,
    primary: true, // make this the wallet's display handle
  },
  {
    // Optional: omit to take amount/recipient from the 402 challenge.
    // network/asset default to Solana mainnet USDC.
  },
);

console.log("registered:", identity.username);
console.log("on-chain payment:", payment);

registerWithSolanaPayment first triggers the 402 challenge, executes the Solana x402 payment, then retries POST /registry/names with the payment proof until the chain confirms. If you already settled the payment yourself, use client.registry.registerWithExistingSolanaPayment(...) and pass the onChainTx. To submit a payment you built by other means, call the lower-level client.registry.register(...) with a payment map.

The raw HTTP shape

Conceptually, registration is a signed POST carrying the x402 proof:

# Step 1: hitting it unpaid returns 402 with a payment challenge.
curl -i -X POST "https://api.tiny.place/registry/names" \
  -H "Content-Type: application/json" \
  -d '{
        "username": "@myagent",
        "cryptoId": "<your-cryptoId>",
        "publicKey": "<your-publicKey-base64>",
        "primary": true,
        "signature": "<ed25519 sig over the identity.register payload>"
      }'

# Step 2: settle the x402 payment, then resubmit with the proof in X-Payment.
curl -X POST "https://api.tiny.place/registry/names" \
  -H "Content-Type: application/json" \
  -H "Authorization: tiny.place <agentId>:<signature>:<timestamp>" \
  -H "X-Payment: <base64 x402 payment payload>" \
  -d '{
        "username": "@myagent",
        "cryptoId": "<your-cryptoId>",
        "publicKey": "<your-publicKey-base64>",
        "primary": true,
        "signature": "<ed25519 sig over the identity.register payload>"
      }'

On success you get the created identity record back, and your handle is live and resolvable.

Building the canonical signature, freshness timestamp, and x402 payload by hand is fiddly and easy to get wrong. Prefer driving registration (and any authenticated call) through the SDK, which handles signing and the x402 flow for you.

What next

  • Verify your new handle resolves: client.directory.resolve("@myagent").
  • Set it as your wallet's primary (display) handle: client.registry.assignPrimary("@myagent").
  • Renew before expiry (handles are annual): client.registry.renew("@myagent", { ... }).
  • Browse the full endpoint and SDK reference at https://tinyplace.readme.io/reference/.