List Payment Links
Retrieve multiple payment links with pagination and filtering options.
Endpoint
Section titled “Endpoint”GET /v1/payment-linksAuthentication
Section titled “Authentication”Requires API key authentication via X-API-Key header.
An account-level key lists every payment link of the account. A project API key lists only the links of its project, that is, the links created with the project’s key (POST /v1/payment-links and the collection endpoints). Links created from the dashboard or with another project’s key are not included, and total does not count them.
Query Parameters
Section titled “Query Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
lim | integer | No | Items per page, 1-100 (default: 10; a value above 100 returns 400) |
off | integer | No | Offset from start (non-negative integer, default: 0) |
status | string | No | Filter by stored status (pending, processing, completed, cancelled, expired). See Expired links |
isTest | boolean | No | Filter by test mode (true or false) |
linkType | string | No | Filter by link type (one_time, customer_collection, customer_payment) |
search | string | No | Only links whose customer name contains this text (1-100 characters) |
paymentProvider | string | No | Accepted but currently ignored for API-key requests: the list and total are not filtered by provider (an unknown value still returns 400). Allowed values: nmi, emetec, yappy, paypal |
Results come newest first (createdAt descending). Unknown query parameters are ignored. The response has no pagination object: to walk every page, increase off by the page size until you have total items (see Paginated Retrieval).
processing means a payment for the link is being confirmed. With PayPal this can last from minutes to several days while PayPal reviews the payment; the link then becomes completed, or goes back to pending if the payment is declined or abandoned.
Expired links
Section titled “Expired links”status=expired only returns links already stored as expired. A pending link past its expiresAt is stored as expired the first time a listing (API or dashboard) returns it on a page; that same response still shows it as pending, and later reads show expired. Because of that, paging with status=pending and off skips links: each page moves the expired ones out of the filtered set, so the next offset lands past links you never saw. To find every expired link, page through without a status filter and keep the links whose status is expired, or pending with an expiresAt in the past (as in Analytics and Reporting). No webhook is sent when a link expires.
Request Examples
Section titled “Request Examples”Basic Request
Section titled “Basic Request”cURL Example
Section titled “cURL Example”curl -X GET "https://api-pay.zelta.dev/v1/payment-links" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json"Node.js Example
Section titled “Node.js Example”const response = await fetch('https://api-pay.zelta.dev/v1/payment-links', { method: 'GET', headers: { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }});
const result = await response.json();console.log(result);Python Example
Section titled “Python Example”import requests
headers = { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json'}
response = requests.get('https://api-pay.zelta.dev/v1/payment-links', headers=headers)result = response.json()print(result)Request with Pagination
Section titled “Request with Pagination”cURL Example
Section titled “cURL Example”curl -X GET "https://api-pay.zelta.dev/v1/payment-links?lim=20&off=20" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json"Node.js Example
Section titled “Node.js Example”const response = await fetch('https://api-pay.zelta.dev/v1/payment-links?lim=20&off=20', { method: 'GET', headers: { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }});
const result = await response.json();console.log(result);Request with Filters
Section titled “Request with Filters”cURL Example
Section titled “cURL Example”curl -X GET "https://api-pay.zelta.dev/v1/payment-links?status=completed&isTest=false" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json"Node.js Example
Section titled “Node.js Example”const response = await fetch('https://api-pay.zelta.dev/v1/payment-links?status=completed&isTest=false', { method: 'GET', headers: { 'X-API-Key': 'your-api-key-here', 'Content-Type': 'application/json' }});
const result = await response.json();console.log(result);Response Format
Section titled “Response Format”Success Response
Section titled “Success Response”{ "success": true, "data": { "paymentLinks": [ { "id": "7312345678901234567", "paymentLinkUrl": "https://pay.zelta.dev/Xk3v9Q_aB7mN2pL8rT4wYz1c", "customerName": "John Doe", "concept": "Consulting Service", "amount": 2500, "status": "pending", "createdAt": "2024-01-15T10:30:00.000Z", "expiresAt": "2024-01-22T10:30:00.000Z", "isTest": true, "redirectUrl": "https://myapp.com/success", "metadata": { "orderId": "ORD-001", "service": "consulting" }, "paymentMethods": ["card", "yappy"], "requestCustomerEmail": true } ], "total": 25 }, "message": "Operation completed successfully", "timestamp": "2024-01-15T10:30:00.000Z"}Response Fields
Section titled “Response Fields”Payment Link Object
Section titled “Payment Link Object”| Field | Type | Description |
|---|---|---|
id | string | Unique payment link identifier (a numeric string) |
paymentLinkUrl | string | URL for the payment link. Its last path segment is the hashUrl used by Get and Cancel |
customerName | string | Name of the customer |
concept | string | Description of what’s being paid for |
amount | integer | Amount in cents |
status | string | Stored payment link status (pending, processing, completed, cancelled, expired). A link past expiresAt can still read pending; see Expired links |
createdAt | string | ISO 8601 timestamp of creation |
expiresAt | string | ISO 8601 timestamp of expiration |
cancelledAt | string (optional) | ISO 8601 timestamp of cancellation. Omitted when not set |
completedAt | string (optional) | ISO 8601 timestamp of completion. Omitted when not set |
isTest | boolean | Whether this is a test payment |
redirectUrl | string (optional) | Custom redirect URL. Omitted when not set |
metadata | object (optional) | Custom metadata. Omitted when not set |
paymentMethods | string[] (optional) | Allowed payment methods (e.g., ["card", "yappy", "paypal"]). Omitted when not set |
requestCustomerEmail | boolean | Whether the payment page requests the customer’s email |
Optional fields are left out when they have no value; they are never null. The list does not include customerEmail; use GET /v1/payment-links/{hashUrl} to read it.
| Field | Type | Description |
|---|---|---|
total | integer | Total number of payment links matching the query, among the links the API key can see |
Status Codes
Section titled “Status Codes”| Code | Description |
|---|---|
200 | OK - Payment links retrieved successfully |
400 | Bad Request - Invalid query parameters (or unknown fields / any expand) |
401 | Unauthorized - Missing X-API-Key header |
403 | Forbidden - Account suspended |
404 | Not Found - Unknown API key (ERR_ACCOUNT_API_KEY_NOT_FOUND) |
429 | Too Many Requests - Rate limit exceeded (plain-text body; wait Retry-After seconds) |
Error Responses
Section titled “Error Responses”Invalid Query Parameters
Section titled “Invalid Query Parameters”Invalid query parameters return 400 with the validation result: error.issues lists each failing parameter (path). Example for lim=500:
{ "success": false, "error": { "issues": [ { "code": "too_big", "maximum": 100, "type": "number", "inclusive": true, "exact": false, "message": "Number must be less than or equal to 100", "path": ["lim"] } ], "name": "ZodError" }}Missing API Key
Section titled “Missing API Key”{ "success": false, "message": "Missing X-API-Key header", "error": { "code": "ERR_API_KEY_MISSING" }, "timestamp": "2024-01-15T10:30:00.000Z"}Implementation Examples
Section titled “Implementation Examples”Basic Payment Links Retrieval
Section titled “Basic Payment Links Retrieval”Node.js Example
Section titled “Node.js Example”async function getAllPaymentLinks(apiKey, options = {}) { try { const params = new URLSearchParams();
if (options.lim) params.append('lim', options.lim); if (options.off) params.append('off', options.off); if (options.status) params.append('status', options.status); if (options.isTest !== undefined) params.append('isTest', options.isTest);
const url = `https://api-pay.zelta.dev/v1/payment-links?${params}`;
const response = await fetch(url, { method: 'GET', headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' } });
if (response.status === 429) { // Rate limited: plain-text body, wait Retry-After seconds const error = new Error('Rate limited'); error.retryAfter = Number(response.headers.get('Retry-After') ?? 60); throw error; }
const result = await response.json();
if (!response.ok) { // 400 validation errors carry error.issues and no message throw new Error(result.message ?? JSON.stringify(result.error?.issues)); }
return result.data; // { paymentLinks, total } } catch (error) { console.error('Error retrieving payment links:', error); throw error; }}
// Usageconst data = await getAllPaymentLinks('your-api-key');console.log('Payment links:', data.paymentLinks);console.log('Total matching links:', data.total);Python Example
Section titled “Python Example”import requests
def get_all_payment_links(api_key, options=None): try: params = {}
if options: if 'lim' in options: params['lim'] = options['lim'] if 'off' in options: params['off'] = options['off'] if 'status' in options: params['status'] = options['status'] if 'isTest' in options: params['isTest'] = str(options['isTest']).lower() # 'true' or 'false'
headers = { 'X-API-Key': api_key, 'Content-Type': 'application/json' }
response = requests.get( 'https://api-pay.zelta.dev/v1/payment-links', headers=headers, params=params )
if response.status_code == 429: # Rate limited: plain-text body, wait Retry-After seconds raise Exception(f"Rate limited, retry in {response.headers.get('Retry-After')}s")
result = response.json()
if not response.ok: # 400 validation errors carry error.issues and no message raise Exception(result.get('message') or result['error'].get('issues'))
return result['data'] except Exception as error: print(f'Error retrieving payment links: {error}') raise
# Usagedata = get_all_payment_links('your-api-key')print('Payment links:', data['paymentLinks'])print('Total matching links:', data['total'])Paginated Retrieval
Section titled “Paginated Retrieval”Node.js Example
Section titled “Node.js Example”async function getAllPaymentLinksPaginated(apiKey) { try { let allPaymentLinks = []; let off = 0; const lim = 100; // maximum page size let hasNext = true;
while (hasNext) { const data = await getAllPaymentLinks(apiKey, { lim, off });
allPaymentLinks = allPaymentLinks.concat(data.paymentLinks); hasNext = data.paymentLinks.length > 0 && off + data.paymentLinks.length < data.total; off += lim; }
return allPaymentLinks; } catch (error) { console.error('Error retrieving all payment links:', error); throw error; }}
// Usageconst allLinks = await getAllPaymentLinksPaginated('your-api-key');console.log(`Retrieved ${allLinks.length} payment links`);Python Example
Section titled “Python Example”def get_all_payment_links_paginated(api_key): try: all_payment_links = [] off = 0 lim = 100 # maximum page size has_next = True
while has_next: data = get_all_payment_links(api_key, {'lim': lim, 'off': off})
all_payment_links.extend(data['paymentLinks']) has_next = len(data['paymentLinks']) > 0 and off + len(data['paymentLinks']) < data['total'] off += lim
return all_payment_links except Exception as error: print(f'Error retrieving all payment links: {error}') raise
# Usageall_links = get_all_payment_links_paginated('your-api-key')print(f'Retrieved {len(all_links)} payment links')Filtered Retrieval
Section titled “Filtered Retrieval”Node.js Example
Section titled “Node.js Example”async function getPaymentLinksByStatus(apiKey, status) { try { const data = await getAllPaymentLinks(apiKey, { status }); return data.paymentLinks; } catch (error) { console.error('Error retrieving payment links by status:', error); throw error; }}
async function getTestPaymentLinks(apiKey) { try { const data = await getAllPaymentLinks(apiKey, { isTest: true }); return data.paymentLinks; } catch (error) { console.error('Error retrieving test payment links:', error); throw error; }}
// Usageconst completedLinks = await getPaymentLinksByStatus('your-api-key', 'completed');const testLinks = await getTestPaymentLinks('your-api-key');Python Example
Section titled “Python Example”def get_payment_links_by_status(api_key, status): try: data = get_all_payment_links(api_key, {'status': status}) return data['paymentLinks'] except Exception as error: print(f'Error retrieving payment links by status: {error}') raise
def get_test_payment_links(api_key): try: data = get_all_payment_links(api_key, {'isTest': True}) return data['paymentLinks'] except Exception as error: print(f'Error retrieving test payment links: {error}') raise
# Usagecompleted_links = get_payment_links_by_status('your-api-key', 'completed')test_links = get_test_payment_links('your-api-key')Use Cases
Section titled “Use Cases”1. Dashboard Overview
Section titled “1. Dashboard Overview”Get recent payment links for dashboard display:
async function getDashboardData(apiKey) { try { // Get recent payment links (last 20) const recentData = await getAllPaymentLinks(apiKey, { lim: 20 });
// Get completed payments for this month const completedData = await getAllPaymentLinks(apiKey, { status: 'completed', lim: 100 });
// Calculate totals const totalCompleted = completedData.paymentLinks.reduce((sum, link) => sum + link.amount, 0); const completedCount = completedData.paymentLinks.length;
return { recentLinks: recentData.paymentLinks, totalCompleted, completedCount, total: recentData.total }; } catch (error) { console.error('Error getting dashboard data:', error); throw error; }}
// Usageconst dashboardData = await getDashboardData('your-api-key');console.log('Dashboard data:', dashboardData);2. Order Management
Section titled “2. Order Management”Get payment links for specific orders:
async function getOrderPaymentLinks(apiKey, orderIds) { try { // Get all payment links const allLinks = await getAllPaymentLinksPaginated(apiKey);
// Filter by order IDs const orderLinks = allLinks.filter(link => link.metadata?.orderId && orderIds.includes(link.metadata.orderId) );
return orderLinks; } catch (error) { console.error('Error getting order payment links:', error); throw error; }}
// Usageconst orderLinks = await getOrderPaymentLinks('your-api-key', ['ORD-001', 'ORD-002']);console.log('Order payment links:', orderLinks);3. Customer Payment History
Section titled “3. Customer Payment History”Get payment history for a specific customer:
async function getCustomerPaymentHistory(apiKey, customerId) { try { // Get all payment links const allLinks = await getAllPaymentLinksPaginated(apiKey);
// Filter by customer ID const customerLinks = allLinks.filter(link => link.metadata?.customerId === customerId );
// Sort by creation date (newest first) customerLinks.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
return customerLinks; } catch (error) { console.error('Error getting customer payment history:', error); throw error; }}
// Usageconst customerHistory = await getCustomerPaymentHistory('your-api-key', 'CUST-123');console.log('Customer payment history:', customerHistory);4. Analytics and Reporting
Section titled “4. Analytics and Reporting”Generate payment analytics:
async function generatePaymentAnalytics(apiKey) { try { // Get all payment links const allLinks = await getAllPaymentLinksPaginated(apiKey);
// Calculate analytics const analytics = { total: allLinks.length, completed: allLinks.filter(link => link.status === 'completed').length, pending: allLinks.filter(link => link.status === 'pending' && new Date(link.expiresAt) > new Date() ).length, processing: allLinks.filter(link => link.status === 'processing').length, cancelled: allLinks.filter(link => link.status === 'cancelled').length, // A link past expiresAt can still read 'pending' (see Expired links) expired: allLinks.filter(link => link.status === 'expired' || (link.status === 'pending' && new Date(link.expiresAt) <= new Date()) ).length, totalAmount: allLinks .filter(link => link.status === 'completed') .reduce((sum, link) => sum + link.amount, 0), averageAmount: 0, statusDistribution: {}, monthlyTrends: {} };
// Calculate average amount if (analytics.completed > 0) { analytics.averageAmount = analytics.totalAmount / analytics.completed; }
// Calculate status distribution allLinks.forEach(link => { analytics.statusDistribution[link.status] = (analytics.statusDistribution[link.status] || 0) + 1; });
// Calculate monthly trends allLinks.forEach(link => { const month = new Date(link.createdAt).toISOString().substring(0, 7); // YYYY-MM if (!analytics.monthlyTrends[month]) { analytics.monthlyTrends[month] = 0; } analytics.monthlyTrends[month]++; });
return analytics; } catch (error) { console.error('Error generating payment analytics:', error); throw error; }}
// Usageconst analytics = await generatePaymentAnalytics('your-api-key');console.log('Payment analytics:', analytics);Best Practices
Section titled “Best Practices”1. Pagination Strategy
Section titled “1. Pagination Strategy”Use appropriate page sizes for your use case:
// For dashboard: small pagesconst dashboardData = await getAllPaymentLinks(apiKey, { lim: 20 });
// For bulk operations: larger pagesconst bulkData = await getAllPaymentLinks(apiKey, { lim: 100 });
// For complete dataset: paginated retrievalconst allData = await getAllPaymentLinksPaginated(apiKey);2. Caching
Section titled “2. Caching”Cache frequently accessed data:
const paymentLinkCache = new Map();
async function getCachedPaymentLinks(apiKey, cacheKey, ttl = 300000) { // 5 minutes const cached = paymentLinkCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < ttl) { return cached.data; }
const data = await getAllPaymentLinks(apiKey);
paymentLinkCache.set(cacheKey, { data: data, timestamp: Date.now() });
return data;}3. Error Handling
Section titled “3. Error Handling”Handle different error scenarios:
async function getPaymentLinksSafely(apiKey, options = {}) { try { return await getAllPaymentLinks(apiKey, options); } catch (error) { if (error.retryAfter) { // 429: wait the Retry-After seconds, then retry once await new Promise(resolve => setTimeout(resolve, error.retryAfter * 1000)); return await getAllPaymentLinks(apiKey, options); }
throw error; }}4. Rate Limiting
Section titled “4. Rate Limiting”Implement rate limiting for batch operations:
async function getPaymentLinksWithRateLimit(apiKey, options = {}, delay = 1000) { try { const data = await getAllPaymentLinks(apiKey, options);
// Rate limiting delay await new Promise(resolve => setTimeout(resolve, delay));
return data; } catch (error) { console.error('Error with rate limiting:', error); throw error; }}Testing
Section titled “Testing”Test Pagination
Section titled “Test Pagination”Test pagination functionality:
// Test first pageconst page1 = await getAllPaymentLinks('your-api-key', { lim: 5, off: 0 });
// Test second pageconst page2 = await getAllPaymentLinks('your-api-key', { lim: 5, off: 5 });
// Test pagination info: total is the same on every pageconsole.log('Page 1:', page1.paymentLinks.length, 'of', page1.total);console.log('Page 2:', page2.paymentLinks.length, 'of', page2.total);Test Filters
Section titled “Test Filters”Test filtering functionality:
// Test status filterconst completedLinks = await getAllPaymentLinks('your-api-key', { status: 'completed' });
// Test test mode filterconst testLinks = await getAllPaymentLinks('your-api-key', { isTest: true });
// Test combined filtersconst completedTestLinks = await getAllPaymentLinks('your-api-key', { status: 'completed', isTest: true});Related Endpoints
Section titled “Related Endpoints”- Get Payment Link - Retrieve a single payment link
- Create Payment Link - Create a new payment link
- Cancel Payment Link - Cancel a payment link