API Reference — v1.0

OnRampDLT Developer Docs

Complete reference for the OnRampDLT platform: XRPL-native token issuance, private Reg D bond offerings, authentication, issuer wallet management, KYC compliance scope, and Stripe billing. All XRPL transactions are constructed server-side and signed client-side via Xaman — private keys never leave your device.

Base URL https://api.onrampdlt.com

Quickstart

Three steps to issue your first token:

  1. Create an accountPOST /api/auth/signup and store the returned JWT.
  2. Register your issuer walletPOST /api/issuer/wallet with your Xaman XRPL r-address. Sign the AccountSet transaction returned to enable DefaultRipple.
  3. Mint a tokenPOST /api/mint with tokenName, tokenSymbol, and network: "xrpl".

For bond issuance you will need a Pro plan subscription and a funded issuer wallet (minimum 12 XRP). See the Bond Platform section.

Example — Signup and first authenticated request (curl)
# 1. Create account curl -X POST https://api.onrampdlt.com/api/auth/signup \ -H "Content-Type: application/json" \ -d '{"email":"you@example.com","password":"YourPassword123!","marketingConsent":false}' # Response: { "success": true, "token": "eyJhbG...", "user": { "id": 42, "email": "you@example.com" } } # 2. Use the token on protected endpoints curl https://api.onrampdlt.com/api/auth/me \ -H "Authorization: Bearer eyJhbG..."

Authentication

All protected endpoints require a valid JWT sent as a Bearer token. The token is returned in the response body at signup and login.

Authorization Header
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Safari and mobile: Safari's ITP blocks cross-site cookies. Store the token from the response body in localStorage and always send it via the Authorization: Bearer header. Cookie auth (auth_token HttpOnly) is supported as a fallback for non-Safari browsers only.

JWTs expire after 30 days and are validated server-side on every request. A token that has been logged out is immediately invalid even if not yet expired. Re-authenticate at POST /api/auth/login to get a fresh token.

Rate Limits

Rate limits are enforced per IP via Cloudflare KV. Exceeding a limit returns HTTP 429 Too Many Requests.

EndpointLimitWindowNotes
POST /api/auth/login5 requests15 minutesPer IP. Protects against credential stuffing.
POST /api/auth/signup5 requests15 minutesPer IP. Turnstile adds bot protection layer.
POST /api/auth/resend-verification3 requests1 hourPer email address. Prevents verification email abuse.
All other endpointsNo hard app limitCloudflare infrastructure-level limits apply for abnormal traffic.
429 response: { "error": "Too many login attempts. Try again later." }

POST /api/auth/signup

Create a new user account. Returns a JWT and user record. Supports optional Cloudflare Turnstile verification.

POST /api/auth/signup Create account
Request Body (JSON)
FieldTypeDescription
emailreqstringUser email address
passwordreqstringPlaintext password (hashed server-side with PBKDF2/SHA-256)
marketingConsentoptbooleanMarketing email opt-in
turnstileTokenoptstringCloudflare Turnstile challenge token
Response 201
{ "success": true, "token": "eyJ...", "user": { "id": 42, "email": "user@example.com" } }
Note: Also sets an auth_token HttpOnly cookie for browser clients. Use the body token for localStorage-based auth on Safari and mobile.

POST /api/auth/login

Authenticate an existing user. Rate-limited to 5 attempts per 15 minutes per IP.

POST /api/auth/login Authenticate user
Request Body (JSON)
FieldTypeDescription
emailreqstringRegistered email
passwordreqstringAccount password
turnstileTokenoptstringCloudflare Turnstile token
Response 200
{ "success": true, "token": "***", "user": { "id": 42, "email": "user@example.com" } }
Rate limit: 429 Too Many Requests after 5 failed attempts in a 15-minute window.
Email verification: If the account email has not been verified, login returns HTTP 403 with { "error": "...", "email_unverified": true, "email": "user@example.com" }. Clients should detect email_unverified: true and surface a resend verification UI. Use POST /api/auth/resend-verification to send a new verification link.

POST /api/auth/logout

Revoke the current JWT and clear the auth cookie. The token is immediately invalid on all subsequent requests — both Bearer header and cookie — regardless of remaining expiry. Revocation is enforced server-side via a KV blacklist with TTL matching the token's remaining validity.

POST /api/auth/logout End session

No request body required. Send the JWT in the Authorization header as usual.

Response 200
{ "success": true }

POST /api/auth/resend-verification

Resend the email verification link to a registered email address. Intended for use when POST /api/auth/login returns HTTP 403 with email_unverified: true. No authentication is required — this endpoint is pre-login. To prevent user enumeration, the response is always { "success": true } regardless of whether the email exists on the platform.

POST /api/auth/resend-verification Resend email verification link — no auth required
Request Body (JSON)
FieldTypeDescription
emailreqstringThe email address to send the verification link to
Response 200
{ "success": true, "message": "If that email is registered and unverified, a verification link has been sent." }
Rate limit: 3 requests per hour per email address. Exceeding this returns HTTP 429. The verification link expires after 24 hours.
Enumeration protection: The response is always { "success": true } — the API does not reveal whether the email address is registered on the platform.

GET /api/auth/me

Return the authenticated user's profile, tier, KYC status, and token mint count. Requires a valid JWT.

GET /api/auth/me Current user profile
Response 200
{
  "id": 42,
  "email": "user@example.com",
  "tier": "investor",
  "first_name": "Jane",
  "last_name": "Smith",
  "display_name": "JaneSmith",
  "mintCount": 0,
  "kyc_status": "verified",
  "accredited_investor": true,
  "created_at": "2026-04-01T00:00:00Z"
}
Profile fields: first_name, last_name, and display_name are null until set via PUT /api/auth/me. These are user-editable display fields, distinct from the legally binding identity in the KYC self-certification record.
has_active_investments: true if the user has at least one bond subscription on record, regardless of current tier. Dashboards should use this to surface the holdings view for lapsed Investor accounts rather than hiding it. Downgrading from Investor to Free never removes investment records.
Tier Values
ValueWalletsCan InvestTokensBondsAPI
free0NoNoNoNo
investor1YesNoNoNo
starter1YesUnlimitedNoNo
pro3YesUnlimited506(b)No
business10YesUnlimited506(b) + 506(c)Yes
enterpriseUnlimitedYesUnlimited506(b) + 506(c)Yes
KYC Status Values
ValueMeaning
not_startedNo KYC or accreditation on file
verifiedSelf-certification complete (506(b)) or Stripe Identity verified (506(c))
expiredKYC verification has passed its validity window
suspendedAccount suspended — contact support

PUT /api/auth/me

Update the authenticated user's display profile. Returns the updated user object in the same shape as GET /api/auth/me.

PUT /api/auth/me Update profile (auth required)
Request Body (JSON) — all fields optional
FieldTypeMax LengthDescription
first_namestring80Given name
last_namestring80Family name
display_namestring80Public display name used in the dashboard
Pass an empty string "" to clear a field back to null. At least one field must be present or the request returns 400.
Response 200 — same shape as GET /api/auth/me
{ "id": 42, "email": "user@example.com", "tier": "investor", "first_name": "Jane", "last_name": "Smith", "display_name": "JaneSmith", ... }

GET /api/tier/features

Return the feature flag set for the authenticated user's current tier. Useful for gating UI elements without re-checking tier strings.

GET /api/tier/features Tier feature flags (auth required)
Response 200
{
  "tier": "investor",
  "can_invest": true,
  "token_issuance": false,
  "bonds_506b": false,
  "bonds_506c": false,
  "api_access": false,
  "wallet_limit": 1,
  "team_seats": 0
}
All tier-gated endpoints return { "error": "...", "upgrade_required": true, "required_tier": "X" } with HTTP 403 when a lower-tier user attempts access.

XAMAN Wallet Authentication

XAMAN (formerly XUMM) is used for QR-based XRPL wallet authentication. Create a payload, display the QR or deep link, then poll status until the user signs.

POST /api/auth/xaman/payload

POST /api/auth/xaman/payload Create XAMAN signin QR (auth required)
Request Body (JSON)
FieldTypeDescription
actionreqstringMust be signin
Response 200
{
  "uuid": "599f5ffc-a258-4662-8b91-2aebffa63045",
  "qr_png": "https://xumm.app/sign/599f5ffc-a258-4662-8b91-2aebffa63045_q.png",
  "deep_link": "https://xumm.app/sign/599f5ffc-a258-4662-8b91-2aebffa63045"
}

Display qr_png as an image for desktop scanning. Use deep_link on mobile to open XAMAN directly. Poll GET /api/auth/xaman/status/:uuid until resolved.

GET /api/auth/xaman/status/:uuid

GET /api/auth/xaman/status/:uuid Poll XAMAN payload result (auth required)
Response 200 — pending
{ "status": "pending" }
Response 200 — resolved
{ "status": "resolved", "account": "rJtG6NREp9FSnKRra7ADmbM5vtw5jAqFDF" }
Poll every 2–3 seconds. On resolved, account contains the verified XRPL wallet address. On expired, create a new payload.

KYC & Accreditation API

The KYC API records accreditation self-certifications for 506(b) bond subscriptions. Full Stripe Identity verification ($6 add-on) is required for 506(c) bond access. Auth required for all endpoints.

GET /api/kyc/status

GET /api/kyc/status KYC and accreditation status (auth required)
Response 200
{
  "kyc_status": "not_started",
  "accredited_investor": false,
  "accredited_method": null,
  "kyc_verified_at": null,
  "kyc_expires_at": null,
  "days_until_expiry": null
}

POST /api/kyc/self-certify

Submit accredited investor self-certification. Creates a verifiable on-platform record with timestamp and IP. Required before subscribing to 506(b) bond offerings.

POST /api/kyc/self-certify Self-certify accredited investor status (auth required)
Request Body (JSON)
FieldTypeDescription
full_namereqstringFull legal name
date_of_birthreqstringFormat: YYYY-MM-DD
address_line1reqstringStreet address
address_line2optstringSuite, apt, etc.
cityreqstringCity
state_provinceoptstringState or province
postal_codereqstringZIP or postal code
countryoptstringISO country code (default: US)
accredited_basisreqstringincome | net_worth | professional
Response 200
{
  "success": true,
  "kyc_status": "verified",
  "accredited_method": "self_cert",
  "message": "Self-certification complete. You can now subscribe to 506(b) bond offerings you have been invited to."
}
506(c) requires more: Self-certification is not sufficient for 506(c) bond access. Full Stripe Identity verification ($6 one-time) is required. Self-cert covers 506(b) invite-only subscriptions only.

Wallet Management

Register and manage XRPL wallets associated with a user account. Wallet limits are enforced by tier. Auth required for all endpoints.

GET /api/wallets

GET /api/wallets List user wallets (auth required)
Response 200
{ "wallets": [
  {
    "id": 1,
    "xrpl_address": "rJtG6NREp9FSnKRra7ADmbM5vtw5jAqFDF",
    "label": "My Main Wallet",
    "wallet_type": "xaman",
    "network": "mainnet",
    "is_default": 1,
    "verified": 1,
    "created_at": "2026-04-01T00:00:00Z"
  }
] }
Response is an object with a wallets array — not a plain array.

POST /api/wallets

Register a new XRPL wallet. Requires Starter tier or above. Wallet limits: free=0, starter=1, pro=3, business=10.

POST /api/wallets Register wallet — Starter+ required
Request Body (JSON)
FieldTypeDescription
xrpl_addressreqstringClassic r-address (25–34 chars, starts with r)
labeloptstringDisplay label, max 50 chars (default: "My Wallet")
wallet_typeoptstringxaman | crossmark | gemwallet (default: xaman)
networkoptstringmainnet | testnet (default: testnet)
set_as_defaultoptbooleanSet as the default wallet for this account
Response 201
{ "success": true, "wallet": { "id": 1, "xrpl_address": "rXXX...", "label": "My Wallet", "wallet_type": "xaman", "network": "testnet", "is_default": 1 } }
Free tier: Returns 403 with { "error": "...", "upgrade_required": true, "required_tier": "starter" }. At limit: returns 403 with current_count and limit fields.
XRPL Validation: The platform verifies that the wallet address actually exists and is funded on the XRPL network before registration. New wallets require at least 10 XRP to be activated. If the address does not exist on the selected network, registration will be rejected with a helpful error message. Blackholed addresses (master key disabled with no regular key) are also blocked.

Issuer Profile

Issuer profiles are public-facing company records that appear on bond listings and investor due diligence pages. Auth required to create or update.

GET /api/issuer/profile

GET /api/issuer/profile Get own issuer profile (auth required)
Response 200
{ "profile": { "company_name": "Acme Capital LLC", "tagline": "Real Assets, Real Returns", "description": "...", "website_url": "https://acme.com", "logo_url": null, "linkedin_url": null, "twitter_url": null, "verified": false } }

Returns { "profile": null } if no profile has been created yet.

POST /api/issuer/profile & PUT /api/issuer/profile

Create or update the issuer profile. Both methods perform an upsert. Auth required.

POST /api/issuer/profile Create/update issuer profile (auth required)
Request Body (JSON) — all fields optional
FieldTypeDescription
company_namestringLegal entity or trade name
taglinestringShort tagline shown on listings
descriptionstringFull company description for investors
website_urlstringCompany website URL
logo_urlstringLogo image URL
linkedin_urlstringLinkedIn company page URL
twitter_urlstringTwitter/X profile URL
Response 200
{ "success": true, "profile": { "company_name": "Acme Capital LLC", ... } }

POST /api/issuer/wallet

Register a Xaman XRPL wallet as the authenticated user's bond issuer wallet. Each account can have one issuer wallet. The wallet must be funded with at least 12 XRP before bonds can be activated.

POST /api/issuer/wallet Register issuer wallet
Request Body (JSON)
FieldTypeDescription
xrplAddressreqstringClassic r-address (starts with r, 25-34 chars)
displayNameoptstringPublic display name for the issuer
Response 201
{
  "id": "a1b2c3d4e5f6",
  "xrplAddress": "rXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "funded": false,
  "message": "Fund with at least 12 XRP before activating bonds.",
  "nextStep": "Enable DefaultRipple via Xaman, then POST /api/bonds",
  "xamanAccountSet": {
    "txjson": { "TransactionType": "AccountSet", "Account": "rXXX...", "SetFlag": 8 },
    "instructions": "Sign in Xaman to enable token issuance"
  }
}
DefaultRipple required: Before issuing bond tokens, sign the AccountSet transaction returned here using Xaman. This enables rippling on your wallet.

GET /api/issuer/wallet

Retrieve the authenticated user's registered issuer wallet, including live XRP balance fetched from XRPL and bond count.

GET /api/issuer/wallet Get issuer wallet + live balance
Response 200
{
  "id": "a1b2c3d4e5f6",
  "xrplAddress": "rXXX...",
  "funded": true,
  "xrpBalance": 45.12,
  "accountExists": true,
  "minRequired": 12,
  "bondCount": 2,
  "xrplExplorer": "https://livenet.xrpl.org/accounts/rXXX..."
}
If no wallet is registered, returns { "wallet": null } with a 200 status.

POST /api/mint

Mint a new XRPL token. Free-tier accounts are limited to 1 token. Pro accounts have unlimited issuance. The API records the mint and returns a transaction reference.

Pro plan required for more than 1 token issuance. Free tier is capped at 1 lifetime mint.
POST /api/mint Create token (auth required)
Request Body (JSON)
FieldTypeDescription
tokenNamereqstringFull token name (e.g. "MyToken")
tokenSymbolreqstringTicker symbol (e.g. "MTK")
networkreqstringMust be xrpl
Response 201
{
  "success": true,
  "token": {
    "name": "MyToken",
    "symbol": "MTK",
    "network": "xrpl",
    "txHash": "xrpl_1711234567890_a1b2c3"
  }
}

GET /api/mints

List all token mints for the authenticated user, ordered by creation date descending.

GET /api/mints List user's tokens (auth required)
Response 200 — Array of mint records
[
  {
    "id": 1,
    "token_name": "MyToken",
    "token_symbol": "MTK",
    "network": "xrpl",
    "tx_hash": "xrpl_1711234567890_a1b2c3",
    "created_at": "2026-01-15T10:30:00Z"
  }
]

Bond Platform

The bond platform enables Reg D-compliant private bond offerings on the XRPL. Bonds are represented as XRPL trust lines — investors hold bond tokens issued by the offering wallet. All signing is done client-side via Xaman; the API only records metadata and verifies payments on-chain.

Pro plan required for bond issuance. Bonds are private offerings — not public securities.
Self-Dealing Prevention: The platform enforces strict separation between issuer and investor wallet roles. Any wallet registered as an issuer wallet (used to create bonds) cannot be used to invest in ANY bond on the platform. Similarly, any wallet used to invest in a bond cannot later be registered as an issuer wallet. This policy ensures regulatory compliance and prevents artificial inflation of subscription metrics.

POST /api/bonds

Create a new bond offering in draft status. The bond is not visible to investors until activated.

POST /api/bonds Create bond (Pro, auth required)
Request Body (JSON)
FieldTypeDescription
namereqstringBond name (e.g. "Series A Bond")
tickerreqstringBond token symbol — max 8 chars. Tickers longer than 3 chars are auto-hex-encoded for XRPL.
totalUnitsreqnumberTotal bond units available for subscription
priceXrpreqnumberPrice per unit in XRP
interestRateBpsreqnumberAnnual interest rate in basis points (e.g. 800 = 8%)
termDaysreqnumberBond term length in days
paymentSchedulereqstringmonthly | quarterly | semiannual | annual
xrpRewardBpsoptnumberEarly-investor XRP reward in basis points of invested amount
descriptionoptstringBond description shown to investors
issuerDisplayNameoptstringPublic issuer name
Response 201
{
  "id": "a1b2c3d4e5f67890",
  "status": "draft",
  "name": "Series A Bond",
  "ticker": "SRAB",
  "tickerHex": "5352414200000000000000000000000000000000",
  "totalUnits": 1000,
  "priceXrp": 100,
  "interestRateBps": 800,
  "termDays": 365,
  "paymentSchedule": "quarterly",
  "metrics": {
    "totalRaiseXrp": 100000,
    "annualInterestXrp": "8000.00",
    "totalXrpRewardPool": "500.00",
    "recommendedWalletFunding": 8520
  },
  "nextStep": "Fund your issuer wallet with at least 520 XRP, then PUT /api/bonds/:id/activate"
}

GET /api/bonds

List bonds by status. Defaults to active. Returns up to 50 bonds.

GET /api/bonds?status=active Public bond listing
Query Parameters
ParamDefaultOptions
statusactivedraft | active | closed
Response 200
{ "bonds": [ { ... } ], "count": 3 }

GET /api/bonds/mine

List all bonds created by the authenticated issuer.

GET /api/bonds/mine Issuer's bonds (auth required)
Response 200
{ "bonds": [ { ... } ], "count": 2 }

GET /api/bonds/my-investments

Return all bond subscriptions belonging to the authenticated user, joined with bond details. No tier gate — available to any authenticated user regardless of current plan, including Free accounts. This endpoint is intentionally ungated so that lapsed Investor subscribers retain permanent access to their holding records.

GET /api/bonds/my-investments Investor holdings — auth required, no tier gate
Response 200
{
  "investments": [
    {
      "subscription_id": 1,
      "bond_id": "a1b2c3d4",
      "investor_address": "rXXX...",
      "amount": "500.00",
      "tx_hash": "ABCD1234...",
      "subscription_status": "confirmed",
      "subscribed_at": "2026-04-01T00:00:00Z",
      "bond_name": "Series A Bond",
      "symbol": "SRAB",
      "bond_type": "506b",
      "interest_rate": "8.00",
      "duration_months": 24,
      "bond_status": "active",
      "issuer_address": "rISSUER...",
      "network": "mainnet"
    }
  ],
  "count": 1
}
Lapse-safe by design: Downgrading from Investor to Free does not remove or restrict access to this endpoint. Investors always own their records. The Investor plan fee gates the ability to subscribe to new offerings — it does not gate access to investments already made.

GET /api/bonds/:id

Public bond detail view. Includes holder count, amount raised, payment schedule, and subscription instructions when the bond is active.

GET /api/bonds/:id Bond details (public)
Response 200
{
  "id": "a1b2c3d4e5f67890",
  "status": "active",
  "name": "Series A Bond",
  "holderCount": 12,
  "amountRaisedXrp": "12000.00",
  "payments": [ { "type": "interest", "scheduledDate": "...", "xrpPerUnit": "2.00", "status": "pending" } ],
  "subscribeInstructions": {
    "step1": "Sign a TrustSet to the issuer for the bond currency",
    "step2": "Send 100 XRP per unit to the issuer wallet",
    "step3": "POST /api/bonds/:id/subscribe with address, units, and payment tx hash"
  }
}

PUT /api/bonds/:id/activate

Activate a draft bond and open it for subscriptions. The issuer must sign an AccountSet transaction (SetFlag 8 — DefaultRipple) in their XRPL wallet and submit the transaction hash. DefaultRipple is required on the issuer wallet before bond tokens can be distributed to investors. Verifies the issuer wallet is funded with at least 12 XRP. Builds the full payment schedule on activation. Bond moves from draft to active status. Auth required — issuer must own the bond.

PUT /api/bonds/:id/activate Activate bond — auth required (issuer only)
Request Body (JSON)
FieldTypeDescription
accountsetTxhashreqstringXRPL transaction hash of the signed AccountSet transaction (SetFlag 8 — DefaultRipple) on the issuer wallet
Response 200
{
  "success": true,
  "bond": {
    "id": "a1b2c3d4e5f67890",
    "status": "active",
    "opensAt": "2026-04-01T00:00:00Z",
    "closesAt": "2026-05-01T00:00:00Z",
    "maturesAt": "2027-04-01T00:00:00Z",
    "paymentCount": 4,
    "bondPageUrl": "https://onrampdlt.io/bonds/a1b2c3d4e5f67890"
  }
}
Irreversible: Activating a bond opens it to investors. Ensure all parameters are correct before activating. Bond status cannot be reverted to draft.
DefaultRipple: The AccountSet transaction sets the DefaultRipple flag (SetFlag 8) on the issuer wallet, enabling bond tokens to be transferred to investor trust lines. Without this, token transfers will fail. The issuer signs this transaction in Xaman, Crossmark, or GemWallet — OnRampDLT never has access to the issuer's private keys.
Form D reminder: A Form D filing reminder is included in the activation confirmation email. Issuers must file Form D with the SEC within 15 calendar days of the first investor subscription. OnRampDLT does not file on behalf of issuers.

POST /api/bonds/:id/subscribe

Record an investor subscription after setting a TrustLine and sending XRP payment. Auth required — unauthenticated calls return 401. The subscriber must have at least an Investor tier account ($49/yr), or any paid issuer plan (Starter+). Free accounts return 403. The API verifies the payment transaction on XRPL before recording the subscription and populates the user_id FK on the subscription record.

POST /api/bonds/:id/subscribe Record investor subscription (public)
Request Body (JSON)
FieldTypeDescription
holderAddressreqstringInvestor's XRPL r-address
unitsreqnumberNumber of bond units to subscribe
paymentTxHashreqstringXRPL transaction hash of the XRP payment to the issuer
Response 201
{
  "success": true,
  "bondId": "a1b2c3d4e5f67890",
  "holderAddress": "rXXX...",
  "units": 5,
  "xrpInvested": 500,
  "xrpRewardExpected": "25.0000 XRP",
  "message": "Subscribed to 5 unit(s). The issuer will send you tokens and reward."
}
Payment verification: The API checks the transaction on XRPL and allows a 2% payment tolerance. The transaction must be finalized before submitting.

GET /api/bonds/:id/xaman/:step

Generate Xaman-ready transaction payloads for issuer operations. Returns the raw txjson to sign in Xaman. Auth required — only the bond issuer can access these.

GET /api/bonds/:id/xaman/:step Xaman signing payloads (auth required)
Step Values
StepDescription
account-setAccountSet tx to enable DefaultRipple on the issuer wallet (required once before issuing)
trust-set-templateTrustSet template to share with investors so they can receive bond tokens
distribute-rewardBatch XRP Payment txs to distribute early-investor rewards to all holders
send-tokensBatch Payment txs to deliver bond tokens to all subscribers
Example Response — send-tokens
{
  "step": "send-tokens",
  "instructions": "Sign each transaction in Xaman to deliver SRAB bond tokens to investors",
  "count": 12,
  "transactions": [
    {
      "holderAddress": "rXXX...",
      "units": 5,
      "txjson": {
        "TransactionType": "Payment",
        "Account": "rISSUER...",
        "Destination": "rXXX...",
        "Amount": { "currency": "5352414200...", "issuer": "rISSUER...", "value": "5" }
      }
    }
  ]
}

Billing

Billing is powered by Stripe. Subscriptions upgrade users from the free tier to Pro or Business. All billing management is handled through the Stripe Customer Portal.

POST /api/subscribe

Create a Stripe Checkout session for upgrading to a paid tier. Returns a checkout URL to redirect the user.

POST /api/subscribe Create Stripe checkout session (auth required)
Request Body (JSON)
FieldTypeDescription
tieroptstringinvestor, starter, pro (default), or business
Response 200
{ "checkoutUrl": "https://checkout.stripe.com/...", "sessionId": "cs_live_..." }

Redirect the user to checkoutUrl. On success, Stripe redirects to /dashboard.html?upgrade=success. On cancel, to /dashboard.html?upgrade=cancelled.

GET /api/manage-billing

Redirect the authenticated user to their Stripe Customer Portal to manage their subscription, payment method, or view invoices. Returns a 302 redirect.

GET /api/manage-billing Redirect to Stripe Customer Portal (auth required)

On success: 302 redirect to the Stripe billing portal.
If no active subscription: 302 redirect to /dashboard.html?billing=no-subscription.

This endpoint performs a redirect, not a JSON response. Use it as an href or window.location target, not a fetch() call.

POST /api/stripe/webhook

Stripe webhook receiver. Handles subscription lifecycle events from Stripe and updates user tier accordingly. This endpoint is called by Stripe — not by API clients directly. All events are verified against the STRIPE_WEBHOOK_SECRET before processing. STRIPE_WEBHOOK_SECRET is required — the Worker hard fails at startup if absent.

POST /api/stripe/webhook Stripe webhook receiver — called by Stripe only

Stripe signs every webhook request with a Stripe-Signature header. The platform verifies this signature using STRIPE_WEBHOOK_SECRET before processing any event. Unverified requests are rejected with HTTP 400.

Handled Events
EventEffect
checkout.session.completedUpgrades user to the subscribed tier (investor, starter, pro, or business)
customer.subscription.deletedDowngrades user to free tier
customer.subscription.updatedUpdates user tier to reflect plan change
Security: STRIPE_WEBHOOK_SECRET is now required and enforced at startup. The Worker will not start without it. Configure this secret in your Cloudflare Workers dashboard under Settings > Variables & Secrets, using the signing secret from your Stripe webhook endpoint configuration.

Security

Authentication & Sessions

Transport & CORS

Non-Custodial Architecture

Payment Verification

Subscription payments (POST /api/bonds/:id/subscribe) are verified against the XRPL before any holding is recorded. The check confirms: transaction type is Payment, destination matches the bond issuer address, sender matches holderAddress, result is tesSUCCESS, and XRP amount is within 2% tolerance of expected. The tolerance prevents minor rounding discrepancies from blocking legitimate subscriptions.

Infrastructure

Required Secrets

The following environment secrets must be set on the Cloudflare Worker. The Worker performs a hard startup failure if any required secret is absent — it will not serve requests in a degraded state.

SecretRequiredNotes
JWT_SECRETRequiredUsed to sign and verify all JWTs. Must be cryptographically strong.
STRIPE_SECRET_KEYRequiredStripe API key for creating checkout sessions and customer portal links.
STRIPE_WEBHOOK_SECRETRequiredStripe webhook signing secret. Used to verify the authenticity of all incoming Stripe webhook events. The Worker performs a hard fail at startup if this secret is absent — it will not process any requests without it. Set this in the Cloudflare Workers dashboard under Settings > Variables & Secrets.
XAMAN_API_KEYRequired for Xaman flowsXAMAN API key for generating QR payloads and polling signing status.
XAMAN_API_SECRETRequired for Xaman flowsXAMAN API secret paired with the API key.
STRIPE_WEBHOOK_SECRET is required — not optional. Prior to Sprint 1, this secret was treated as optional and the webhook handler would process events without signature verification if absent. This is no longer the case. The Worker will hard fail on startup if STRIPE_WEBHOOK_SECRET is not set. All Stripe webhook events are verified using this secret before any processing occurs.

Error Codes

All error responses return JSON with an error field. The HTTP status indicates the category of failure.

StatusCategoryCommon Causes
400Bad RequestMissing required fields, invalid values, business rule violations (duplicate wallet, wrong bond status), payment verification failure.
401UnauthorizedNo JWT, expired JWT, invalid signature, session deleted.
403ForbiddenAuthenticated but not permitted — free tier trying to issue bonds, modifying another issuer's bond.
404Not FoundBond ID does not exist. Route does not exist.
429Too Many RequestsIP rate limit exceeded on login or signup. Retry after window expires.
500Internal Server ErrorUnexpected API failure. Logged server-side. Retry once — if persisting, contact support.
Error Response Format
{ "error": "Human-readable description of what went wrong" }
Error messages are designed to be shown directly to developers. The one exception is login/signup credential errors — these use a generic message intentionally to prevent user enumeration.

KYC & Compliance

Platform KYC Role

OnRampDLT is a technology platform, not a broker-dealer, investment adviser, placement agent, or transfer agent. The platform provides software tools for issuers to create and manage token and bond offerings on the XRP Ledger. It does not act as an intermediary in securities transactions, does not hold investor funds, and does not make suitability determinations.

Issuer responsibility: Issuers using this platform to conduct Regulation D offerings are solely responsible for: (1) determining investor eligibility and accredited investor status, (2) filing Form D with the SEC within 15 days of first sale, (3) ensuring compliance with applicable state blue sky laws, (4) performing bad actor diligence under Rule 506(d), and (5) maintaining offering records required by the Securities Act. The platform does not perform these functions.

What OnRampDLT Verifies vs. Does Not Verify

VerificationStatusNotes
Email address uniqueness✓ Platform-verifiedMust be unique for account creation
XRPL r-address format✓ Platform-verifiedClassic r-address format validated at registration
Issuer wallet XRPL balance✓ Platform-verifiedLive balance checked via XRPL at bond activation
Investor XRP payment on-chain✓ Platform-verifiedPayment tx verified against XRPL before recording subscription
Accredited investor status✗ Not verifiedIssuer responsibility
Investor identity (KYC/AML)✗ Not verifiedIssuer responsibility
Investor suitability✗ Not verifiedIssuer responsibility
Form D filing✗ Not handledIssuer responsibility — file at SEC EDGAR
State blue sky compliance✗ Not handledIssuer responsibility

Reg D Requirements by Offering Type

RequirementRule 506(b)Rule 506(c)
Investor eligibilityUp to 35 non-accredited sophisticated investors; unlimited accredited investorsAccredited investors only
General solicitationProhibitedPermitted
Accredited investor verificationSelf-certification acceptableIssuer must take reasonable steps to verify — third-party letters, tax returns, bank statements, or verification service
Form D filing deadline15 calendar days after first sale15 calendar days after first sale
Resale restrictionsRestricted securities — Rule 144 holding period (1 year for non-reporting issuers)Same
Bad actor checkRule 506(d) requiredRule 506(d) required

Legal Overview

This section describes the regulatory framework, issuer obligations, and investor protections applicable to offerings conducted using this platform. Issuers and their counsel should read this section in full before conducting any offering.

OnRampDLT is a software-as-a-service technology platform. Its functions are limited to providing software tools, recording offering metadata, generating Xaman-compatible XRPL transaction payloads for client-side signing, and verifying that XRPL payment transactions exist on-chain.

OnRampDLT does not:

Not a securities exchange or ATS: OnRampDLT does not operate an alternative trading system (ATS), securities exchange, or secondary market. Bond tokens are restricted securities and may not be freely traded. Any secondary market activity may require separate SEC registration.

Rule 506(b) — Private Offerings

Rule 506(c) — General Solicitation Permitted

Form D

File electronically through SEC EDGAR within 15 calendar days of first sale. Failure to file may result in loss of the exemption. The platform does not file Form D on behalf of issuers.

Resale Restrictions

Securities sold under Reg D are restricted securities under Rule 144. Investors may not resell without: a satisfied holding period (1 year for non-reporting issuers), and satisfaction of Rule 144 conditions, or a separate registration or exemption. Issuers must ensure investors receive written notice. Subscription agreements should include a restrictive legend.

Bad Actor Disqualification (Rule 506(d))

Issuers must verify no "covered person" — the issuer entity, its directors, officers, 20%+ beneficial owners, placement agents and affiliates — is subject to a disqualifying event (SEC orders, criminal convictions, court injunctions, regulatory sanctions). Diligence must be performed before first sale and periodically thereafter. The platform does not perform this check.

DataStoredNotes
Email addressYes — Cloudflare D1Required for account
PasswordHash only (PBKDF2/SHA-256)Plaintext never stored
XRPL wallet address (public)Yes — D1 + KVPublic on-chain data
Bond offering parametersYes — KV storeDuration of bond
Investor XRPL addresses + holdingsYes — KV storeAssociated with bond record
Private keys / seed phrasesNeverNon-custodial by design
Payment card dataNeverStripe handles all payment processing
Government-issued IDNot collectedPlatform does not perform KYC
Session tokens (JWT)Yes — D1, 30-day TTLDeleted on logout
Stripe customer IDYes — D1For billing management
All data is processed within Cloudflare's infrastructure. For GDPR or CCPA data requests, contact support@onrampdlt.io. See the Privacy Policy for full details.

Glossary

Accredited InvestorUnder SEC Rule 501: individual with income >$200K (or $300K joint) in each of the two most recent years, net worth >$1M excluding primary residence, or certain professional credentials (Series 7/65/82 license holders).
Basis Points (bps)Unit of interest rate measurement. 1 bps = 0.01%. 100 bps = 1%. 800 bps = 8% annual interest rate. Used for interestRateBps and xrpRewardBps fields.
Bad ActorUnder SEC Rule 506(d), a covered person subject to a disqualifying event (criminal conviction, SEC order, regulatory sanction, etc.) that disqualifies the offering from the Regulation D exemption.
Classic AddressAn XRPL wallet address beginning with "r", 25–34 characters long. Only classic addresses are accepted — X-addresses are not supported.
DefaultRippleAn XRPL account flag (AccountSet SetFlag 8) required on an issuer wallet before issuing tokens via trust lines. Must be signed once through Xaman before any bond token can be sent to investors.
Form DAn SEC filing required within 15 days of first sale in a Reg D offering. Filed electronically through SEC EDGAR. Provides notice to the SEC but does not constitute approval.
General SolicitationAny public communication advertising a securities offering — social media, press releases, websites, or public seminars. Prohibited under 506(b). Permitted under 506(c) with accredited-only restriction.
Issued CurrencyXRPL's native mechanism for custom tokens. An issuer wallet issues tokens that flow through trust lines to holder wallets. No smart contracts required. Tickers >3 chars are 40-character hex strings.
JWTJSON Web Token (RFC 7519). OnRampDLT uses HS256-signed JWTs for API authentication. Valid for 30 days, server-side validated — logged-out tokens are immediately rejected.
Regulation DSEC rules providing exemptions from Securities Act of 1933 registration requirements for private placements. Rules 506(b) and 506(c) are the most commonly used exemptions.
Restricted SecuritiesSecurities acquired in unregistered, private sales. Subject to Rule 144 resale restrictions — generally a 1-year holding period for non-reporting companies before public resale.
Rule 144SEC safe harbor for resale of restricted securities. Key conditions: holding period (6–12 months), current public information, volume limits, manner of sale, broker notification.
tesSUCCESSXRPL transaction result code indicating successful application to the ledger. OnRampDLT verifies this code when confirming investor payment transactions at subscription.
tickerHex40-character hex-encoded ticker for XRPL. Required when ticker is more than 3 characters. Computed automatically. Used in TrustSet and Payment transactions.
TrustLineXRPL mechanism whereby a wallet explicitly grants trust to hold a specific issued currency from a specific issuer. Investors must sign a TrustSet in Xaman before receiving bond tokens.
TrustSetThe XRPL transaction type for creating or modifying a trust line. Investors sign TrustSet transactions to authorize receipt of bond tokens from the issuer wallet.
XamanNon-custodial XRPL wallet app (formerly XUMM). Used for all XRPL transaction signing on OnRampDLT. The platform generates txjson; the user reviews and signs in Xaman. Xaman broadcasts to the XRPL network.
XRPLXRP Ledger — a decentralized public blockchain with native issued currency support, ~3-5s finality, and low fees. All token and bond operations on this platform occur on XRPL mainnet.

Changelog

v1.2.0
April 4, 2026 — Current version
  • Added: POST /api/auth/resend-verification — resend email verification link, rate limited 3/hour per email, no auth required
  • Updated: POST /api/auth/login now returns HTTP 403 with { email_unverified: true, email } when email is not verified
  • Updated: PUT /api/bonds/:id/activate now requires accountsetTxhash in body — the signed AccountSet transaction hash (SetFlag 8 — DefaultRipple)
  • Added: POST /api/stripe/webhook documented — STRIPE_WEBHOOK_SECRET is now required (hard fail at startup if absent)
  • Added: Investor tier ($49/year) documented across all tier references — positioned between free and starter
  • Added: Required Secrets table in Security section documenting STRIPE_WEBHOOK_SECRET enforcement
  • Added: POST /api/subscribe now accepts investor and starter as valid tier values
v1.1.0
April 4, 2026
  • Fixed: GET /api/auth/me was returning 401 for all email-auth users — SQL query referenced columns not present in live DB
  • Fixed: JWT logout now performs server-side revocation via Cloudflare KV blacklist — Bearer tokens are rejected immediately after logout
  • Fixed: GET /api/tokens was publicly accessible with no auth — auth gate added
  • Fixed: POST /api/tokens/xaman/payload had no auth or tier gate — now requires Starter tier
  • Fixed: POST /api/auth/signup returned 500 on empty body — now returns 400 with descriptive error
  • Fixed: POST /api/auth/signup accepted invalid email formats — email regex validation added
  • Added: POST /api/issuer/profile route (was PUT-only, now both POST and PUT perform upsert)
  • Added: GET /api/tier/features endpoint returning all feature flags for current tier
  • Corrected: API base URL is api.onrampdlt.com (not api.onrampdlt.io)
  • Documented: Crossmark and GemWallet support — both verified as valid wallet_type values in the wallets API
  • Documented: KYC self-certify endpoint with correct field names
  • Documented: XAMAN wallet auth flow (payload + status polling)
  • Documented: Issuer profile API (GET/POST/PUT)
  • Documented: Wallet management API with tier limits
v1.0.0
April 2026 — Initial release
  • Initial public API release
  • Email/password auth with JWT session management and PBKDF2/SHA-256 password hashing
  • Issuer wallet registration with live XRPL balance checking
  • Token issuance (POST /api/tokens) with tier enforcement — Starter required
  • XAMAN QR-based token trust-set flow (POST /api/tokens/xaman/payload)
  • Bond platform — full lifecycle: create, activate, subscribe, Xaman signing payloads
  • Reg D tier gating — 506(b) requires Pro, 506(c) requires Business
  • On-chain payment verification with 2% tolerance for subscription recording
  • Bond interest payment schedule generation at activation
  • Early-investor XRP reward distribution via Xaman batch signing
  • Stripe billing — Starter, Pro, and Business tier subscriptions with Customer Portal
  • Automatic tier upgrade/downgrade via Stripe webhook events
  • KYC self-certification (506(b)) and Stripe Identity verification (506(c))
  • Team member management for Business/Enterprise accounts
  • Admin backend for user management, email broadcasts, and audit log review

Versioning Policy

The API follows semantic versioning. Breaking changes (removed fields, changed response structures, deleted endpoints) will increment the major version with a minimum 90-day deprecation notice. Additive changes (new endpoints, new optional fields) are non-breaking. All endpoints are currently under v1 — no version prefix is required in the URL path.