1 | |
2 | # SplitRoute API Documentation - AI Code Context |
3 | |
4 | ## Overview |
5 | SplitRoute is a payment processing API for Nano cryptocurrency that enables instant revenue sharing and payment distribution. |
6 | Create standard, perpetual, or simulated invoices, split payments among multiple recipients, and automate payouts. |
7 | |
8 | \``` |
9 | Key Benefits: |
10 | - Split payments instantly to creators, partners & teams |
11 | - Up to 10x cheaper than PayPal & Stripe |
12 | - Payouts in under 1 minute |
13 | - Unlimited recipients per invoice |
14 | \``` |
15 | |
16 | ## Authentication |
17 | |
18 | All authenticated endpoints require an API key in the header: |
19 | |
20 | \```http |
21 | X-API-Key: your_api_key_here |
22 | \``` |
23 | |
24 | ### API Key Tiers |
25 | |
26 | | Tier | Rate Limit | Features | Notes | |
27 | | :---------- | :------------------------ | :----------------------------- | :---------------------------------- | |
28 | | No AUTH | 1000 requests/hour shared | Basic API access | Shared limit across all unauth users | |
29 | | FREE | 1000 requests/hour | Basic API access, Dashboard | Individual limit | |
30 | | BASIC | 2000 requests/hour | Basic API access, Dashboard | Individual limit | |
31 | | PRO | 5000 requests/hour | Advanced features, Dashboard | Individual limit | |
32 | | ENTERPRISE | 20000+ requests/hour | Custom limits, Support | Individual limit | |
33 | |
34 | ### Register for a Free API Key |
35 | |
36 | \``` |
37 | POST /api/v1/api-keys/register |
38 | |
39 | Request: |
40 | { |
41 | "email": "[email protected]", |
42 | "name": "your.key.name" |
43 | } |
44 | |
45 | Response (Example): |
46 | { |
47 | "key": "SR_SECRET_d069a1d9a53ff1a77296d0223eb000ac", # Your secret API key |
48 | "name": "your.key.name", |
49 | "tier": "FREE", |
50 | "status": "ACTIVE", |
51 | "rate_limit_per_hour": 1000, |
52 | "remaining_invoices": -1, # Credits system (often -1 for unlimited on certain tiers) |
53 | "created_at": "2025-03-25T19:06:33.107954Z", |
54 | "expires_at": "2124-03-01T19:06:33.107943Z", # For time-limited tiers |
55 | "is_active": true |
56 | } |
57 | \``` |
58 | |
59 | ### Upgrade to Paid Tier |
60 | |
61 | \``` |
62 | POST /api/v1/api-keys/purchase |
63 | |
64 | Headers: |
65 | X-API-Key: your_current_api_key |
66 | |
67 | Request: |
68 | { |
69 | "tier": "BASIC", # Required. One of: BASIC, PRO, ENTERPRISE |
70 | "billing_period": "monthly" # Optional. One of: monthly, annual (default: monthly) |
71 | } |
72 | |
73 | Response: |
74 | # Returns a standard Invoice object (see below) to pay for the upgrade. |
75 | # The invoice's user_data field contains details about the purchase. |
76 | \``` |
77 | |
78 | ## Core Endpoints |
79 | |
80 | ### Create an Invoice |
81 | |
82 | Creates a standard (expiring) or perpetual (non-expiring) invoice. Can also simulate creation. |
83 | |
84 | \``` |
85 | POST /api/v1/invoices |
86 | |
87 | Headers: |
88 | X-API-Key: your_api_key # Optional for free tier, required for paid features/limits |
89 | |
90 | # Request Body: |
91 | # Use EITHER destinations OR forward_account |
92 | { |
93 | "nominal_amount": 29.9, # Required for STANDARD invoices. Ignored for PERPETUAL_OPTIONAL. |
94 | "nominal_currency": "EUR", # Required for STANDARD invoices. Ignored for PERPETUAL_OPTIONAL. (e.g., USD, EUR, XNO) |
95 | "destinations": [ # Option 1: Define multiple payout destinations (rules applied on successful payment) |
96 | { |
97 | "account": "nano_1primary...", # Nano address |
98 | "primary": true # Required for one destination if using this structure without equal_split logic. Receives remainder. |
99 | }, |
100 | { |
101 | "account": "nano_2percent...", |
102 | "percentage": 10, # Percentage of nominal_amount (after fixed amounts & fees) |
103 | "description": "Partner fee" # Optional description |
104 | }, |
105 | { |
106 | "account": "nano_3fixed...", |
107 | "nominal_amount": 5 # Fixed amount in nominal_currency |
108 | } |
109 | ], |
110 | # "forward_account": "nano_...", # Option 2: Alternative to 'destinations'. Forwards 100% (after fees) to this single address. |
111 | "webhook_url": "https://www.example.com/webhook", # Optional: URL for real-time status updates |
112 | "webhook_secret": "your_webhook_secret", # Optional: Secret to verify webhook signature |
113 | "reference": "custom-order-id-123", # Optional: Your internal reference string |
114 | "show_qr": true, # Optional: Include base64 QR code in response (default: false) |
115 | "is_perpetual": false, # Optional: Create a non-expiring invoice (default: false). nominal_amount/currency are ignored if true. |
116 | "simulate": false # Optional: Calculate result without creating invoice (default: false) |
117 | # "user_data": "any custom string data", # Optional: Custom data, included in webhooks |
118 | } |
119 | |
120 | # Response Body (Full Invoice Object - also used for GET endpoints): |
121 | { |
122 | "invoice_id": "INV_2025_03_d43d2995c143be1b59df", # Public ID |
123 | "secret_id": "INV_SECRET_27b6888c19fa0a83d01da", # Secret ID (keep private) |
124 | "account_address": "nano_1payment...", # Address for payer to send funds |
125 | "invoice_type": "STANDARD", # STANDARD | PERPETUAL_OPTIONAL |
126 | "nominal_currency": "EUR", # Original request currency (null for PERPETUAL_OPTIONAL) |
127 | "formatted_currency": "XNO", # Payment currency (Nano) |
128 | "required": { # Amount details required (null for PERPETUAL_OPTIONAL `amount` fields) |
129 | "unit_amount": "30567346000000000000000000000000", # Amount in raw units (smallest Nano unit) |
130 | "formatted_amount": "30.567346", # Human-readable XNO amount |
131 | "nominal_amount": "29.9" # Original requested nominal amount |
132 | }, |
133 | "received": { # Amount details received so far |
134 | "unit_amount": "0", |
135 | "formatted_amount": "0", |
136 | "nominal_amount": "0" # Approximate nominal value received |
137 | }, |
138 | "exchange_rate": "0.978168", # Exchange rate used (1 nominal = X XNO), null if not applicable |
139 | "expires_at": "2025-03-25T19:29:10.159811Z", # Expiry time (null for PERPETUAL) |
140 | "paid_at": null, # Timestamp when fully paid (null if not paid) |
141 | "forwarded_at": null, # Timestamp when funds forwarded (null if not forwarded) |
142 | "done_at": null, # Timestamp when processing complete (null if not done) |
143 | "timestamp_created": "2025-03-25T19:14:10.159811Z", # Creation time |
144 | "destinations": [ # Calculated distribution amounts (includes service fee) |
145 | { |
146 | "type": "primary", # Destination type (primary, percentage, fixed, service_fee) |
147 | "account": "nano_1primary...", |
148 | "unit_amount": "22246178670000000000000000000000", |
149 | "formatted_amount": "22.24617867", |
150 | "nominal_amount": "21.760" # Approx. nominal value for this destination |
151 | }, |
152 | { |
153 | "type": "percentage", |
154 | "account": "nano_2percent...", |
155 | "unit_amount": "3056734600000000000000000000000", |
156 | "formatted_amount": "3.0567346", |
157 | "nominal_amount": "2.990", |
158 | "description": "Partner fee" |
159 | }, |
160 | { |
161 | "type": "fixed", |
162 | "account": "nano_3fixed...", |
163 | "unit_amount": "5111596000000000000000000000000", |
164 | "formatted_amount": "5.111596", |
165 | "nominal_amount": "5.000" |
166 | }, |
167 | { |
168 | "type": "service_fee", # Automatically added service fee destination |
169 | "account": "nano_1nowapi...", # SplitRoute's fee account |
170 | "unit_amount": "152836730000000000000000000000", |
171 | "formatted_amount": "0.15283673", |
172 | "nominal_amount": "0.149", |
173 | "description": "Service fee" |
174 | } |
175 | ], |
176 | "service_fee_rate": "0.50", # Service fee percentage applied (e.g., 0.50 for 0.5%) |
177 | "show_qr": true, # Matches request parameter |
178 | "is_simulation": true, # True if created with simulate: true |
179 | "is_pending": false, # True if partially paid (but not fully) |
180 | "is_paid": false, # True if required amount received (or any amount for PERPETUAL_OPTIONAL) |
181 | "is_forwarded": false, # True if funds sent to destinations/forward_account |
182 | "is_done": false, # True if processing complete (paid & forwarded/settled or expired) |
183 | "is_expired": false, # True if expires_at passed and not paid (STANDARD only) |
184 | "is_overpaid": false, # True if received > required (STANDARD only) |
185 | "is_perpetual": false, # Matches request parameter |
186 | "qr_code": "base64_string_with_encoded_qr-code", # Optional: Base64 encoded QR code PNG if show_qr=true |
187 | "uri_rfc_8905": "payto:nano/nano_1payment...?amount=...", # RFC 8905 payment URI |
188 | "uri_nano": "nano:nano_1payment...?amount=..." # Common Nano payment URI |
189 | # user_data is typically NOT included in the response, only in webhooks |
190 | } |
191 | \``` |
192 | |
193 | ### Get Invoice by ID |
194 | |
195 | Retrieves the full Invoice object by its public ID. |
196 | |
197 | \``` |
198 | GET /api/v1/invoices/{invoice_id} |
199 | |
200 | Response: Full Invoice Object (see Create Invoice Response) |
201 | \``` |
202 | |
203 | ### Get Invoice by Secret ID |
204 | |
205 | Retrieves the full Invoice object by its secret ID. |
206 | |
207 | \``` |
208 | GET /api/v1/invoices/secret/{secret_id} |
209 | |
210 | Response: Full Invoice Object (see Create Invoice Response) |
211 | \``` |
212 | |
213 | ### Get Supported Currencies |
214 | |
215 | Lists currencies usable in the `nominal_currency` field. |
216 | |
217 | \``` |
218 | GET /api/v1/currencies |
219 | |
220 | Response: |
221 | [ |
222 | { |
223 | "code": "USD", |
224 | "name": "US Dollar", |
225 | "is_fiat": true, |
226 | "decimal_places": 6, # Max precision for calculations |
227 | "min_amount": "0.000001", |
228 | "max_amount": "25000.00" |
229 | }, |
230 | # ... other fiat currencies (EUR, GBP, etc.) |
231 | { |
232 | "code": "XNO", |
233 | "name": "Nano", |
234 | "is_fiat": false, |
235 | "decimal_places": 6, # Max precision for display/nominal input |
236 | "min_amount": "0.000001", |
237 | "max_amount": "25000.00" # Equivalent value limit |
238 | } |
239 | # Potentially other cryptos if supported |
240 | ] |
241 | \``` |
242 | |
243 | ## Payment Distribution Types |
244 | |
245 | When creating an invoice using `destinations`, you define how funds are split: |
246 | |
247 | ### 1. Primary Destination (`primary: true`) |
248 | Receives the remainder after Service Fee, Fixed Amounts, and Percentage Amounts are calculated. Only one primary allowed. |
249 | \```json |
250 | { "account": "nano_1abc...", "primary": true, "description": "Main recipient" } |
251 | \``` |
252 | |
253 | ### 2. Percentage-Based Distribution (`percentage`) |
254 | Receives a specified percentage of the `nominal_amount` (after fixed amounts & fees are deducted). |
255 | \```json |
256 | { "account": "nano_1def...", "percentage": 25.5, "description": "Partner (25.5%)" } |
257 | \``` |
258 | |
259 | ### 3. Fixed Amount Distribution (`nominal_amount`) |
260 | Receives a specific amount in the invoice's `nominal_currency`. Calculated after Service Fee but before Percentages. |
261 | \```json |
262 | { "account": "nano_1ghi...", "nominal_amount": 5.00, "description": "Platform fee ($5.00)" } |
263 | \``` |
264 | |
265 | ### Calculation Waterfall & Example |
266 | |
267 | Distributions are calculated in this order from the total received: |
268 | 1. **Service Fee:** (e.g., 0.5% for FREE tier) |
269 | 2. **Fixed Amounts:** Destinations with `nominal_amount`. |
270 | 3. **Percentage Amounts:** Destinations with `percentage` (calculated on remaining balance). |
271 | 4. **Primary Destination:** Receives the final remaining balance. |
272 | |
273 | **Example:** Invoice for $100.00 USD (Free Tier - 0.5% fee) |
274 | \```json |
275 | { |
276 | "nominal_amount": 100.00, "nominal_currency": "USD", |
277 | "destinations": [ |
278 | { "account": "nano_1primary...", "primary": true, "description": "Seller" }, |
279 | { "account": "nano_2partner...", "percentage": 20, "description": "Partner (20%)" }, |
280 | { "account": "nano_3platform...", "nominal_amount": 10.00, "description": "Platform Fee ($10)" } |
281 | ] |
282 | } |
283 | \``` |
284 | Distribution: |
285 | 1. Service Fee: $0.50 (to SplitRoute) -> Remaining: $99.50 |
286 | 2. Fixed (Platform): $10.00 -> Remaining: $89.50 |
287 | 3. Percentage (Partner): 20% of $89.50 = $17.90 -> Remaining: $71.60 |
288 | 4. Primary (Seller): $71.60 |
289 | |
290 | ## Invoice Lifecycle (Standard Invoices) |
291 | |
292 | 1. **Created**: Ready for payment (`is_paid`, `is_expired`, etc. are false). |
293 | 2. **Pending**: Partially paid (`is_pending: true`). |
294 | 3. **Paid**: Full required amount received (`is_paid: true`). |
295 | 4. **Forwarded**: Funds sent to destinations (`is_forwarded: true`). |
296 | 5. **Done**: Processing complete (Paid & Forwarded) (`is_done: true`). |
297 | 6. **Expired**: Expired before full payment (`is_expired: true`, alternative end state). |
298 | |
299 | *(Perpetual invoices do not expire and may have multiple paid/forwarded cycles).* |
300 | |
301 | ## Real-time Payment Monitoring |
302 | |
303 | ### WebSocket Connection |
304 | |
305 | Receive real-time invoice updates. |
306 | |
307 | **URL:** `wss://api.splitroute.com/api/v1/ws/invoices/{invoice_id}` |
308 | |
309 | **Message Format (Example: `invoice.paid` event):** |
310 | \```json |
311 | { |
312 | "timestamp": "2025-03-31T09:18:11.897603+00:00", // Message send time |
313 | "message_type": "invoice", // "invoice" or "payment" |
314 | "category": "updated", // "updated", "completed", "failed", "payment" |
315 | "payload": { // Event-specific data |
316 | "id": "INV_2025_03_...", // Invoice ID |
317 | "timestamp": "2025-03-31T09:18:11.890364+00:00", // Event occurrence time |
318 | "event_type": "invoice.paid", // Specific event identifier |
319 | "unit_amount": "2000000000000000000000000", // Amount relevant to event (raw units) |
320 | "formatted_amount": "0.000002", // Amount relevant to event (formatted XNO) |
321 | "nominal_amount": "0.000002", // Approx. nominal value of event amount |
322 | "is_paid": true, |
323 | "is_expired": false, |
324 | "currency": "EUR", // Invoice nominal currency (if applicable) |
325 | "exchange_rate": "0.8221560000" // Invoice exchange rate (if applicable) |
326 | // Other fields like is_overpaid, transaction_id may appear depending on event_type |
327 | } |
328 | } |
329 | \``` |
330 | |
331 | **Event Categories & Types:** |
332 | |
333 | | Category | Description | Example Event Types | |
334 | | :---------- | :----------------------------- | :---------------------------------------------- | |
335 | | `updated` | Invoice status changed | `invoice.paid`, `invoice.forwarded` | |
336 | | `completed` | Invoice processing finished | `invoice.done` | |
337 | | `failed` | Invoice expired or failed | `invoice.expired` | |
338 | | `payment` | Payment block confirmed | `payment.confirmed` | |
339 | |
340 | *(See full docs for detailed payload structure per event type)* |
341 | |
342 | **JS Example:** |
343 | \```javascript |
344 | const invoiceId = 'INV_...'; // Your Invoice ID |
345 | const socket = new WebSocket(`wss://api.splitroute.com/api/v1/ws/invoices/${invoiceId}`); |
346 | |
347 | socket.onopen = (e) => console.log('WebSocket Connected'); |
348 | socket.onerror = (error) => console.error(`WebSocket Error: ${error.message}`); |
349 | socket.onclose = (event) => console.log(`WebSocket Closed: Code=${event.code}, Reason=${event.reason}`); |
350 | |
351 | socket.onmessage = (event) => { |
352 | const message = JSON.parse(event.data); |
353 | const payload = message.payload; |
354 | console.log(`Received WS [${message.category}] ${payload.event_type} for ${payload.id}`); |
355 | |
356 | if (payload.event_type === 'invoice.paid') { |
357 | console.log(`Invoice Paid! Amount: ${payload.formatted_amount} XNO`); |
358 | // Your logic here (e.g., update UI, fulfill order) |
359 | } else if (payload.event_type === 'payment.confirmed') { |
360 | console.log(`Payment Confirmed: ${payload.formatted_amount} XNO (Tx: ${payload.transaction_id.substring(0,8)}...)`); |
361 | // Useful for showing payment progress, especially for perpetual invoices |
362 | } |
363 | |
364 | if (message.category === 'completed' || message.category === 'failed') { |
365 | console.log(`Invoice ${payload.id} finished processing (Category: ${message.category}).`); |
366 | socket.close(); |
367 | } |
368 | }; |
369 | \``` |
370 | |
371 | **Connection Limits:** 5 per invoice, 15 min inactivity timeout, closes 5 min after done/expired. |
372 | |
373 | ### Webhook Integration |
374 | |
375 | Receive server-side notifications for invoice events. |
376 | |
377 | **Setup:** Provide `webhook_url` and optional `webhook_secret` during invoice creation. |
378 | |
379 | **Events:** `invoice.created`, `invoice.paid`, `invoice.expired`, `invoice.forwarded`, `invoice.done` |
380 | |
381 | **Verification:** Check `X-Webhook-Signature` header using HMAC-SHA256 (`timestamp` + `request_body`) with your `webhook_secret`. |
382 | |
383 | \```python |
384 | # Example Python Signature Verification |
385 | import hmac, json, hashlib |
386 | |
387 | def verify_signature(secret: str, timestamp: str, body: bytes, received_signature: str) -> bool: |
388 | payload_str = body.decode('utf-8') # Assuming UTF-8 encoded JSON body |
389 | signed_content = f"{timestamp}{payload_str}".encode('utf-8') |
390 | expected_signature = hmac.new(secret.encode('utf-8'), signed_content, hashlib.sha256).hexdigest() |
391 | return hmac.compare_digest(expected_signature, received_signature) |
392 | |
393 | # In your webhook handler: |
394 | # timestamp = request.headers.get('X-Webhook-Timestamp') |
395 | # signature = request.headers.get('X-Webhook-Signature') |
396 | # body_bytes = await request.body() |
397 | # is_valid = verify_signature(YOUR_WEBHOOK_SECRET, timestamp, body_bytes, signature) |
398 | \``` |
399 | |
400 | ## Error Handling |
401 | |
402 | API errors return a JSON body: |
403 | \```json |
404 | { |
405 | "error": "error_code_string", # e.g., "validation_error", "invalid_api_key" |
406 | "message": "Human-readable message", |
407 | "details": { # Optional: Field-specific validation errors |
408 | "field_name": "Error description for this field" |
409 | } |
410 | } |
411 | \``` |
412 | Common Status Codes: `200` (OK), `201` (Created), `400` (Bad Request), `401` (Unauthorized), `403` (Forbidden), `404` (Not Found), `422` (Unprocessable Entity - Validation), `429` (Too Many Requests), `5xx` (Server Error). |
413 | |
414 | Rate limit info in headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. |
415 | |
416 | ## Base URL |
417 | `https://api.splitroute.com` |
418 | |
419 | ## Core Implementation Flow (JS Example) |
420 | \```javascript |
421 | async function processOrder(orderId, amount, currency) { |
422 | const apiKey = 'your_api_key'; // Use environment variables |
423 | const destinations = [ /* your destination rules */ ]; |
424 | |
425 | try { |
426 | // 1. Create Invoice |
427 | const createResponse = await fetch('https://api.splitroute.com/api/v1/invoices', { |
428 | method: 'POST', |
429 | headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey }, |
430 | body: JSON.stringify({ |
431 | nominal_amount: amount, |
432 | nominal_currency: currency, |
433 | destinations: destinations, |
434 | reference: orderId, |
435 | webhook_url: 'your_webhook_url' // Optional: if using webhooks |
436 | }) |
437 | }); |
438 | |
439 | if (!createResponse.ok) { |
440 | const errorData = await createResponse.json(); |
441 | throw new Error(`Invoice creation failed: ${errorData.message}`); |
442 | } |
443 | const invoice = await createResponse.json(); |
444 | console.log(`Invoice ${invoice.invoice_id} created. Payer address: ${invoice.account_address}`); |
445 | // Display payment info (address, amount, QR code) to the user |
446 | |
447 | // 2. Monitor via WebSocket (or rely on Webhooks) |
448 | const socket = new WebSocket(`wss://api.splitroute.com/api/v1/ws/invoices/${invoice.invoice_id}`); |
449 | socket.onmessage = (event) => { |
450 | const message = JSON.parse(event.data); |
451 | if (message.payload.event_type === 'invoice.paid') { |
452 | console.log(`Order ${orderId} Payment Confirmed!`); |
453 | // Fulfill order, update database, etc. |
454 | socket.close(); |
455 | } else if (message.category === 'failed') { |
456 | console.log(`Order ${orderId} Invoice Expired.`); |
457 | // Handle expiry (e.g., cancel order) |
458 | socket.close(); |
459 | } |
460 | }; |
461 | // Handle socket errors and closures appropriately |
462 | |
463 | } catch (error) { |
464 | console.error(`Error processing order ${orderId}:`, error); |
465 | // Handle error (e.g., notify admin, show error to user) |
466 | } |
467 | } |
468 | \``` |
469 | |