Documentation
Getting started, authentication, and API reference.
PayKit provides Stellar-based payments, wallets, checkout, swaps, and fiat on/off ramps. To integrate:
- Sign in with Google above. You will be redirected back with a JWT used for the developer dashboard.
- Create an organization (e.g. "Acme Payments") from the Developer Overview or via
POST /organizations. - Create an API key under API Keys. Store the key securely; it is shown only once and cannot be retrieved later.
- Use the key in all API requests:
x-api-key: pk_xxxorAuthorization: Bearer pk_xxx.
Base URL for API requests: NEXT_PUBLIC_API_URL (e.g. https://api.paykit.io). All request/response bodies are JSON unless noted.
Dashboard (OAuth): Sign in via GET /auth/google; you are redirected to Google and back with a JWT. The JWT is used for developer-only endpoints (organizations, API keys, billing, usage, treasury list).
API (API key): For payments, wallets, checkout, swap, yield, and merchant endpoints, send your API key in every request: header x-api-key: <key> or Authorization: Bearer <key>. The key is tied to an organization; rate limits and usage are applied per organization.
Never expose your API key in client-side code. Use a backend server or proxy to call PayKit with the key.
Direct payment: POST /payment — Send from a PayKit wallet to any Stellar address. Body: { "fromWalletId", "toAddress", "asset", "amount" }. Returns transaction hash. Supported assets: XLM, USDC, PYUSD.
Checkout (hosted): POST /merchant/checkout/create — Requires x-api-key. Body: { "amount", "asset", "success_url?", "cancel_url?", "description?", "auto_yield?" }. Returns session id and temporary wallet address. Set auto_yield: true to allocate incoming funds to yield (e.g. 50% liquid, 50% yield strategy).
Merchant payout: POST /merchant/payout — Withdraw from merchant settlement balance. Body: { "destination", "asset", "amount" }. Destination is a Stellar public key.
POST /payment
Body: { "fromWalletId": "...", "toAddress": "G...", "asset": "USDC", "amount": "10" }
→ { "txHash": "..." }
POST /merchant/checkout/create
Headers: x-api-key: <key>
Body: { "amount": "25", "asset": "USDC", "success_url": "https://...", "cancel_url": "https://..." }
→ { "id", "walletAddress", "amount", "asset", "status", "expiresAt" }POST /wallet/create — Creates a new Stellar wallet (keypair generated and encrypted server-side). Optional body: { "userId" } to associate with a user. Returns id, publicKey, createdAt. New accounts are funded via Friendbot on testnet.
GET /wallet/:id — Returns wallet details. GET /wallet/:id/balance — Returns balances for XLM, USDC, PYUSD (from Horizon).
POST /wallet/create
Body: { "userId?": "..." }
→ { "id", "publicKey", "createdAt" }
GET /wallet/:id/balance
→ { "balances": [{ "asset", "balance", "limit?" }] }Embedded wallets let your users hold and send crypto without exporting keys. You identify users by email or provider + providerId. One wallet is created per identity; private keys are encrypted and never exposed.
• POST /wallet/embedded/create — Body: { "email"? | "provider", "providerId" }. Returns walletId, publicKey, createdAt. Idempotent: same email returns same wallet.
• POST /wallet/embedded/sign — Body: { "walletId", "envelopeXdr" } (base64 XDR transaction envelope). Server signs with the wallet key and returns signedEnvelopeXdr. Submit the signed XDR to Horizon yourself or use PayKit payment APIs.
• GET /wallet/embedded/balance?walletId=... — Returns balances for XLM, USDC, PYUSD.
const client = createClient({ apiKey: "pk_xxx" });
const { walletId, publicKey } = await client.createEmbeddedWallet({ email: "user@example.com" });
const balances = await client.getEmbeddedBalance(walletId);GET /treasury/list — Requires auth. Returns treasuries for the current user. POST /treasury/create — Body: { "name" }. GET /treasury/balance?treasuryAccountId=... — Returns balances and yield info.
Strategies (Earn): GET /treasury/strategies — Returns available strategies: Conservative (80% RWAs, 20% liquidity), Balanced (50% lending, 30% RWAs, 20% liquidity), Yield Max (40% lending, 40% LP, 20% RWAs). Each includes expectedApy and allocation. POST /treasury/strategies/enable — Body: { "treasuryAccountId", "strategy": "conservative" | "balanced" | "yield_max" }.
GET /treasury/strategies → { "strategies": [{ "id", "name", "description", "expectedApy", "allocation" }] }
POST /treasury/strategies/enable
Body: { "treasuryAccountId": "...", "strategy": "balanced" }
→ { "treasuryAccountId", "strategy" }Swaps use Stellar path payments: you send a fixed amount of one asset and receive the maximum amount of another asset via the best available path. The destination is the same wallet (swap in place). Supported assets: XLM, USDC, PYUSD.
Quote: GET /swap/quote?walletId=...&fromAsset=...&toAsset=...&amount=... — Returns estimated amount out without executing. Response: { "fromAsset", "toAsset", "sendAmount", "estimatedAmountOut", "pathAvailable" }. Use this to show users an estimate before they confirm.
Execute: POST /swap — Body: { "walletId", "fromAsset", "toAsset", "amount" }. Returns txHash and status. fromAsset and toAsset must differ.
GET /swap/quote?walletId=...&fromAsset=USDC&toAsset=XLM&amount=100
→ { "fromAsset": "USDC", "toAsset": "XLM", "sendAmount": "100", "estimatedAmountOut": "12.5", "pathAvailable": true }
POST /swap
Body: { "walletId", "fromAsset": "USDC", "toAsset": "XLM", "amount": "100" }
→ { "txHash", "fromAsset", "toAsset", "amount", "status": "success" }Requires API key (merchant or organization). Deposit USDC (or other assets) into the pool; withdraw anytime. Positions are tracked per organization/merchant.
• GET /liquidity/pool — Public. Returns { "totalLiquidity", "apr", "poolId" }.
• GET /liquidity/positions — Returns your positions. POST /liquidity/deposit — Body: { "asset?", "amount" }. POST /liquidity/withdraw — Body: { "positionId", "amount?" } (omit amount to withdraw full position).
POST /liquidity/deposit Body: { "asset": "USDC", "amount": "1000" }
POST /liquidity/withdraw Body: { "positionId": "...", "amount": "500" }
GET /liquidity/positions → { "positions": [{ "positionId", "asset", "amount", "poolId", "apr", "createdAt" }] }Credit limit is derived from the treasury's USDC balance. No auth required; pass treasuryAccountId.
• GET /credit/limit?treasuryAccountId=... — Returns { "limitAmount", "borrowedAmount", "availableAmount", "currency" }.
• POST /credit/borrow — Body: { "treasuryAccountId", "amount" }. Draw from credit line.
• POST /credit/repay — Body: { "treasuryAccountId", "amount" }. Repay outstanding balance.
GET /credit/limit?treasuryAccountId=...
→ { "limitAmount", "borrowedAmount", "availableAmount", "currency": "USDC" }
POST /credit/borrow Body: { "treasuryAccountId", "amount": "5000" }
POST /credit/repay Body: { "treasuryAccountId", "amount": "1000" }Anchors provide fiat on/off ramps and asset issuance. You request a redirect URL to the anchor's interactive flow; the user completes KYC and transfer on the anchor's site. If account is a PayKit wallet public key, the server performs SEP-10 auth with the anchor and sends the JWT so the request is accepted (avoids 403).
• POST /anchor/deposit — Body: { "anchorDomain", "assetCode", "account", "email?", "memo?" }. Returns url (and optional id). Redirect the user to url.
• POST /anchor/withdraw — Body: { "anchorDomain", "assetCode", "account", "type?", "email?", "memo?" }. Returns url for the withdraw flow.
anchorDomain is the anchor's domain (e.g. testanchor.stellar.org). account is the Stellar public key that will receive (deposit) or send (withdraw); if it is a PayKit-managed wallet, SEP-10 is used automatically.
POST /anchor/deposit
Body: { "anchorDomain": "testanchor.stellar.org", "assetCode": "USDC", "account": "G...", "email": "user@example.com" }
→ { "url": "https://...", "id?" }GET /fx/quote?from=...&to=...&amount=...&walletId? — Returns { "from", "to", "fromAmount", "toAmount", "rate", "route" }. Supported pairs: USD↔USDC, USDC↔XLM, etc. Optional walletId for crypto↔crypto (uses swap quote).
GET /fx/quote?from=USD&to=USDC&amount=100
→ { "from": "USD", "to": "USDC", "fromAmount": "100", "toAmount": "100", "rate": "1", "route": ["USD", "USDC"] }Onramp (buy): POST /onramp/buy — Body: { "walletId", "asset?", "fiatAmount?", "fiatCurrency?" }. Returns widgetUrl, sessionId, provider. Open the URL in an iframe or new tab; the user completes KYC and payment with the provider. Crypto is sent to the given walletId.
Offramp (withdraw): POST /onramp/withdraw — Body: { "walletId", "asset?", "amount" }. Initiates a withdrawal to bank/card. Returns withdrawalId, status. Requires sufficient balance in the wallet.
const { widgetUrl } = await client.onrampBuy({ walletId, fiatAmount: 100, fiatCurrency: "USD" });
window.open(widgetUrl); // or embed in iframeYield is earned on assets deposited from a treasury. Supported assets: XLM, USDC (PYUSD if configured).
• POST /yield/deposit — Body: { "treasuryAccountId", "asset", "amount" }. Moves funds from the treasury into the yield provider. Returns positionId?, asset, amount.
• POST /yield/withdraw — Body: { "treasuryAccountId", "asset", "amount" }. Withdraws from yield back to the treasury.
• GET /yield/positions — Query: asset?. Returns positions: array of { "asset", "principal", "yieldEarned", "total", "apy" }.
POST /yield/deposit Body: { "treasuryAccountId", "asset": "USDC", "amount": "100" }
POST /yield/withdraw Body: { "treasuryAccountId", "asset": "USDC", "amount": "50" }
GET /yield/positions → { "positions": [{ "asset", "principal", "yieldEarned", "total", "apy" }] }Subscribe to a single stream to receive all supported event types as they occur. No authentication required on the stream URL; events are broadcast to all subscribers.
• GET /events/stream — SSE endpoint. Use EventSource in the browser or an SSE client in your backend. Each message is a JSON object: { "id", "type", "data", "createdAt" }.
• GET /events/recent?limit=50 — Returns the last N events (for replay or polling). Requires API key.
Event types: payment.created, payment.completed, wallet.created, swap.executed, yield.updated, checkout.completed.
const es = new EventSource("https://api.paykit.io/events/stream");
es.onmessage = (e) => {
const event = JSON.parse(e.data);
console.log(event.type, event.data);
};POST /webhooks/register — Body: { "url", "events": ["payment.completed", "checkout.completed", "wallet.created", "treasury.updated", ...], "secret?" }. When an event occurs, PayKit sends a POST to your url with a JSON payload. Optionally provide a secret to verify signatures. Retries are performed on failure.
Your endpoint should respond with 2xx quickly. Process asynchronously if needed. Validate the payload and idempotency (same event id may be delivered more than once).
POST /webhooks/register
Body: { "url": "https://yourapp.com/webhooks", "events": ["payment.completed", "checkout.completed"], "secret": "whsec_..." }POST /blockchain/webhook — Body: { "url", "event"? | "events"?, "secret?" }. Similar to webhooks above but scoped to blockchain-related events. Events: payment.completed, payment.created, payment.failed, wallet.created, swap.executed, checkout.completed, checkout.failed.
POST /blockchain/webhook
Body: { "url": "https://yourapp.com/blockchain-events", "events": ["payment.completed", "swap.executed"] }Install: npm install paykit-sdk. Create a client with createClient({ apiKey, baseUrl? }). Use namespaced API: client.treasury, client.swap, client.liquidity, client.credit, client.yield, client.fx, client.payments, client.wallet.
import { createClient } from "paykit-sdk";
const client = createClient({ apiKey: "pk_xxx", baseUrl: "https://api.paykit.io" });
// Treasury (Earn / strategies)
const strategies = await client.treasury.getStrategies();
await client.treasury.enableStrategy({ treasuryAccountId: "...", strategy: "balanced" });
const { balances } = await client.treasury.getBalance(treasuryAccountId);
// Swap
const quote = await client.swap.getQuote({ walletId, fromAsset: "USDC", toAsset: "XLM", amount: "100" });
if (quote.pathAvailable) await client.swap.execute({ walletId, fromAsset: "USDC", toAsset: "XLM", amount: "100" });
// Liquidity
await client.liquidity.deposit({ asset: "USDC", amount: "1000" });
const { positions } = await client.liquidity.positions();
// Credit
const limit = await client.credit.getLimit(treasuryAccountId);
await client.credit.borrow({ treasuryAccountId, amount: "5000" });
// Payments (checkout with auto-yield)
await client.payments.create({ amount: "100", asset: "USDC", autoYield: true });
// FX
const fxQuote = await client.fx.quote({ from: "USD", to: "USDC", amount: "100" });
// Yield
const { positions } = await client.yield.getPositions();Errors: On failure the API returns an appropriate status (4xx/5xx) and a JSON body with error: a human-readable message. Example: { "error": "Wallet not found" }. Use the message for logging; do not rely on exact strings for logic.
Rate limits: Applied per organization (derived from API key). Limits depend on plan: e.g. Free 10 req/s, Pro 50 req/s, Premium 200 req/s. When exceeded you receive 429; retry after the suggested delay. Monthly API request limits also apply (see Billing).
Idempotency: For payment and checkout creation you can send Idempotency-Key header with a unique value to avoid duplicate charges on retries.