Skip to main content

Quickstart

This is the fast path: from zero to a settled USDC payment in under 30 minutes, entirely in sandbox (no real funds). Every step shows what you should get back.

You will:

  1. Get a sandbox API key and log in with the CLI.
  2. Create a wallet.
  3. Fund it from the faucet.
  4. Find your Permara ID and send a payment.
Prerequisites

Node.js 20+. For the SDK: pnpm add @permara/sdk. For the CLI, build it once with pnpm --filter @permara/cli build (see the CLI guide).

1. Get a key

API keys are minted per tenant. Grab a sandbox key (pm_test_…) from the Permara developer dashboard, then save it to a local CLI profile:

permara login pm_test_...
# validates the key by resolving your tenant, then writes ~/.permara/config.json

Expected output confirms the tenant and masks the key:

Logged in · sandbox
tenant Acme Robotics (acme-robotics)
key pm_test_Ab12Cd34Ef56_…
profile sandbox → ~/.permara/config.json

For the SDK and curl, keep the key in an env var instead:

export PERMARA_API_KEY=pm_test_...
note

The base URL is inferred from the key prefix — pm_test_https://api.sandbox.permara.com. See Environments.

2. Create a wallet

A wallet is a Safe multisig. Create one owned by a signer address you control. In sandbox the CLI generates and stores a demo signer for you on first use.

curl -X POST https://api.sandbox.permara.com/v1/wallets \
-H "X-SafeBank-Api-Key: $PERMARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Treasury",
"owners": [{ "address": "0xYourSignerAddress", "role": "ADMIN_OWNER" }],
"threshold": 1,
"chainId": 84532
}'

You get back a Wallet. The address is a predicted Safe address at first; status moves PENDING_DEPLOYMENT → DEPLOYING → DEPLOYED.

{
"id": "w_1a2b3c",
"address": "0x7Fb2…c41A",
"status": "PENDING_DEPLOYMENT",
"chainId": 84532,
"threshold": 1,
"predictedAddress": true,
"name": "Acme Treasury",
"owners": [{ "address": "0xYourSignerAddress", "role": "ADMIN_OWNER", "label": null }],
"createdAt": "2026-08-09T12:00:00.000Z"
}

Keep the id — you need it for the next steps.

3. Fund it from the faucet

Sandbox only. The faucet drips test USDC (default 250, capped at 1000 per drip).

curl -X POST https://api.sandbox.permara.com/v1/wallets/w_1a2b3c/fund \
-H "X-SafeBank-Api-Key: $PERMARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "asset": "usdc", "amount": "250" }'

You get back a Drip. status is SUBMITTED and becomes CONFIRMED once the transfer lands on-chain:

{
"id": "d_9z8y",
"walletId": "w_1a2b3c",
"asset": "usdc",
"amount": "250",
"chainId": 84532,
"status": "SUBMITTED",
"txHash": null,
"failureReason": null,
"createdAt": "2026-08-09T12:00:05.000Z"
}

4. Your Permara ID

Every business on Permara has a generated payment identity — a Permara ID of the form did:permara:<64 lowercase hex>. It is created with the business and becomes payable once the business is verified; nobody claims or chooses one. Payers address it directly, by the business's verified name, or by an email that invites the business if it is not on Permara yet.

# Your identities
curl https://api.sandbox.permara.com/v1/dids/me \
-H "X-SafeBank-Api-Key: $PERMARA_API_KEY"

# Does a Permara ID resolve to a payable business, and whose?
curl "https://api.sandbox.permara.com/v1/dids/resolve?did=did:permara:7f5d…9e8d" \
-H "X-SafeBank-Api-Key: $PERMARA_API_KEY"

The public DID document is served at GET /v1/dids/<did>/did.json, and the W3C DID Resolution binding at GET /1.0/identifiers/<did>; neither carries a payment endpoint or any personal data. To pay someone who is not on Permara yet, reserve an identity for their email or mobile number (POST /v1/dids/reservations) and sign against it — the same contact always returns the same identity.

Legacy wallet handles

PUT /v1/directory/handle (a @handle pointing at a Safe wallet on the legacy rail) still answers for existing integrations and is being removed. New integrations address a Permara ID, an email, or an address.

5. Send a payment

A payment is proposed → signed → executed. The API proposes it (screening

  • calldata), you sign the returned safeTxHash locally with a raw secp256k1 signature, and submitting the signature auto-executes once the wallet's threshold is met.
# 1. Propose — returns a payment with a safeTxHash to sign.
curl -X POST https://api.sandbox.permara.com/v1/payments \
-H "X-SafeBank-Api-Key: $PERMARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sourceWalletId": "w_1a2b3c",
"destination": { "type": "handle", "handle": "volt-components" },
"amount": { "value": "25.00" }
}'

# 2. Sign the safeTxHash locally (raw secp256k1 — NOT personal_sign), then:
curl -X POST https://api.sandbox.permara.com/v1/payments/p_abc/sign \
-H "X-SafeBank-Api-Key: $PERMARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "signature": "0x<signature-over-safeTxHash>" }'

After signing, the payment settles. status goes to COMPLETED and executedTxHash is populated:

{
"id": "p_abc",
"sourceWalletId": "w_1a2b3c",
"destinationType": "handle",
"destinationHandle": "volt-components",
"destinationAddress": "0x…",
"destinationChainId": 84532,
"asset": "usdc",
"amount": "25.00",
"status": "COMPLETED",
"safeTxHash": "0x…",
"executedTxHash": "0x…",
"memo": null,
"failureReason": null,
"createdAt": "2026-08-09T12:01:00.000Z"
}

That is the whole loop.

Next steps