Quick Start

Your first end-to-end pass against a live YieldFabric — log in, query, send a payment, poll until it settles. Real endpoints, copy-paste friendly.

What you'll build

The minimum end-to-end flow: log in, resolve your caller identity, query something on the federated gateway, submit a payments mutation that lands on chain, poll until the messages reaches a terminal state. Every snippet below uses an endpoint that exists in the running services — not a sketch.

This mirrors the examples/yieldfabric-quickstart reference TypeScript starter and the YF Python SDK's poll_message_completion pattern. Larger flows (multi-party deals, obligation lifecycle, collateralised swaps) are the same loop with richer payloads — see the DMS guide.

The public API surface

A consumer app talks to a small allow-list of YieldFabric surfaces and nothing else. Memorise this — most drift in client integrations comes from reaching into service-to-service paths.

POSTIdentity
auth.yieldfabric.com/auth/**

REST: login, refresh, signature signin, API-key issuance/exchange, /protected/jwt, /auth/users/me.

GETMessage status
pay.yieldfabric.com/api/users/.../messages/...

REST polling for the message returned by on-chain mutations.

POSTGraphQL gateway
api.yieldfabric.com/graphql

Federated GraphQL reads and mutations spanning auth + payments + agents, including payment, obligation, and swap execution.

Use the GraphQL gateway for every GraphQL operation and payments REST for message-status polling after async on-chain work. Same JWT. Full rationale

Prerequisites

  • A YieldFabric account in a running deployment (your own dev environment, or a hosted one).
  • curl and jq for the inline snippets (or substitute your language of choice — every step is plain HTTP).
  • The base URLs your app may talk to. Defaults shown below — substitute your own if you're running a self-hosted deployment.
export YF_AUTH=https://auth.yieldfabric.com       # sign-in + JWT issuance
export YF_GATEWAY=https://api.yieldfabric.com     # federated GraphQL
export YF_PAYMENTS=https://pay.yieldfabric.com    # REST message polling
export YF_AGENTS=https://agents.yieldfabric.com   # working groups, threads, streams
01
Identity

Log in (browser users) or exchange an API key (backends)

Both paths return the same access JWT — 15-min lifetime, 30-day single-use refresh. Use email + password for human users; use an API key for any server-side caller (operators, app backends, batch jobs). API keys are revocable per-key and auditable, and they keep plaintext passwords out of env vars.

# Browser / human-driven: email + password
LOGIN=$(curl -s -X POST $YF_AUTH/auth/login \
    -H "Content-Type: application/json" \
    -d '{
      "email": "user@example.com",
      "password": "your-password"
    }')

export TOKEN=$(echo "$LOGIN" | jq -r .token)
export REFRESH=$(echo "$LOGIN" | jq -r .refresh_token)
export USER_ID=$(echo "$LOGIN" | jq -r .user.id)

# Backend service: exchange a long-lived API key for a short-lived JWT
# (issue the key once with POST /auth/api-key/generate; store as $API_KEY)
# LOGIN=$(curl -s -X POST $YF_AUTH/auth/api-key \
#   -H "Content-Type: application/json" \
#   -d "{\"api_key\":\"$API_KEY\"}")
# export TOKEN=$(echo "$LOGIN" | jq -r .token)

Same JWT works against every service. Tokens last 15 min; refresh via POST $YF_AUTH/auth/refresh with the refresh_token (single-use — store the new one immediately).

02
Resolve caller

Look up your own identity

/protected/jwt decodes the bearer and returns the caller's claims — user id, account address, default wallet id. You'll thread the wallet id into nearly every mutation below (it's the canonical way to refer to yourself or a counterparty without leaking entity-name resolution into your client).

PROFILE=$(curl -s $YF_AUTH/protected/jwt \
    -H "Authorization: Bearer $TOKEN")

export USER_ID=$(echo "$PROFILE" | jq -r .user_id)
export WALLET_ID=$(echo "$PROFILE" | jq -r .default_wallet_id)
echo "User: $USER_ID · Wallet: $WALLET_ID"

GET $YF_AUTH/auth/users/me returns the same caller from the user-table side (with role, email, is_external_signer). Either works — use /protected/jwt when you only need the claims, /users/me when you also want the role/profile metadata.

03
Read

Query the federated gateway

The federated gateway at api.yieldfabric.com/graphql is the read endpoint your app talks to for cross-subgraph queries. Same JWT, joined views: entity lookups, the wallet activity feed, and payment/contract listings.

# Recent activity on your wallet — merged payments + messages, newest first
curl -s -X POST $YF_GATEWAY/graphql \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "query($w: String!) { walletFlow { activity(walletId: $w, limit: 25) { nextCursor items { __typename ... on PaymentActivity { timestamp payment { id status amount assetId } } ... on MessageActivity { timestamp message { id messageType status executed } } } } } }",
      "variables": { "w": "'$WALLET_ID'" }
    }' | jq

cursor is opaque — pass nextCursor from the previous page back in as cursor for the next page. nextCursor: null means exhausted. Same (walletId, limit, cursor) triple returns the same page on any replica.

04
Write

Send a payment

The instant mutation enqueues a confidential on-chain transfer. The mutation returns as soon as the GraphQL gateway accepts the message — actual settlement is async. Pass destinationWalletId when you have the recipient's wallet UUID (canonical) or destinationId if you only have an entity name / email.

# Substitute with your recipient's wallet id. Use /protected/jwt off
# their bearer (or look up the entity) to get a real UUID.
export RECIPIENT_WALLET=00000000-0000-0000-0000-000000000000

SEND=$(curl -s -X POST $YF_GATEWAY/graphql \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "mutation($input: InstantSendInput!) { instant(input: $input) { success messageId paymentId } }",
      "variables": {
        "input": {
          "destinationWalletId": "'$RECIPIENT_WALLET'",
          "assetId": "aud-token-asset",
          "amount": "10",
          "idempotencyKey": "'"$(uuidgen)"'"
        }
      }
    }')

export MESSAGE_ID=$(echo "$SEND" | jq -r .data.instant.messageId)
echo "Enqueued: $MESSAGE_ID"

Amount is an integer string (no decimals). idempotencyKey must be a fresh UUIDv4 per call — never a deterministic hash like sha256(user+action); the resolver dedupes on it and a retry after a stuck message just returns the stuck messageId again. Omitting the key is also fine (the resolver auto-generates one).

05
Settle

Poll until terminal

On-chain execution is asynchronous. The canonical pattern for backend callers (Python SDK, shell scripts, operator) is REST polling at 2-second intervals against /api/users/{user_id}/messages/{message_id} — simpler than SSE, no missed-event race. Terminal when executed is non-null.

until curl -s -H "Authorization: Bearer $TOKEN" \
    "$YF_PAYMENTS/api/users/$USER_ID/messages/$MESSAGE_ID" \
    | jq -e '.executed' > /dev/null; do
  echo "Waiting…"
  sleep 2
done

curl -s -H "Authorization: Bearer $TOKEN" \
    "$YF_PAYMENTS/api/users/$USER_ID/messages/$MESSAGE_ID" | jq

Status progression: Pending → Validating → Executing → Completed (or Failed / ManualSignature). The "executed" field flips when the on-chain receipt lands. Cap your polling at ~300s and surface failures from the response envelope.

What just happened

You ran the canonical YF loop in five HTTP calls:

  • 01IdentityPOST /auth/login (or API-key exchange) returned an access JWT.
  • 02Resolve/protected/jwt gave you your user id and default wallet id.
  • 03Read — the federated gateway returned a merged activity feed.
  • 04Write — the GraphQL gateway accepted an instant mutation and enqueued the on-chain work.
  • 05Settle — REST polling on the messages endpoint flipped to executed.

Everything richer than this — obligations, swaps, multi-party deals, collateralised repo, agent-mediated negotiation — is the same loop. The mutation changes, but the four-step shape (resolve identity → write → poll → render) does not.

Troubleshooting

401 unauthorized

Token expired (15-min lifetime). Refresh via POST $YF_AUTH/auth/refresh with the refresh_token. Same JWT works across all services — you only need one. Refresh is single-use: store the new refresh_token returned in the response atomically.

403 Insufficient permissions: CryptoOperations

The vault sign path needs the CryptoOperations permission on the JWT. Users with role SuperAdmin / Admin / Manager get it from the role-default fallback; narrower roles (Viewer, ApiClient, User) need it explicitly granted via POST /auth/users/{id}/permissions.

No entity found with name "0x..."

You passed a wallet address into a name-shaped field (counterpart / obligor / destinationId). Those fields are entity-name lookups. Use the matching *WalletId field with a UUID instead (destinationWalletId, counterpartWalletId, obligorWalletId). /protected/jwt returns your own default_wallet_id.

GraphQL says success: true but execution never completes

The mutation returns when the operation is queued; actual on-chain execution is async. Poll GET $YF_PAYMENTS/api/users/$USER_ID/messages/$MESSAGE_ID every 2s until `executed` is set. Manual-signature mode pauses for the user to sign; insufficient-balance / counterpart-not-found surface as a failed message status.

403 on /api/mq/submit

That endpoint is service-to-service only and needs a vault-equipped caller. Don't call it from app code — use the equivalent GraphQL gateway mutation on $YF_GATEWAY/graphql (instant, createObligation, acceptObligation, etc.) which calls into the same pipeline with proper input normalisation.

YieldFabric docs(317)