Payments API¶
The Payments API enables you to create checkout sessions, process payments, manage payment intents, list your transactions, and refund them. Most endpoints require authentication via API Key; the buyer-facing endpoints use the session ID as their credential.
Base URL
https://api.4geeks.io
Authentication¶
Merchant endpoints require the X-Api-Key header:
The buyer-facing endpoints (/session/{id}, /session/{id}/status, /intent, /subscription) require no key: the unguessable session ID is the credential. Every session is bound to the merchant that created it, and every read is scoped to that merchant.
See Authentication for details.
Amounts and currencies¶
amount is always expressed in major units ("99.99"), while the amount of a refunded payment is expressed in the currency’s minor unit (cents). The API converts amounts with the currency’s real precision: zero-decimal currencies (JPY, KRW, CLP…) are not multiplied by 100, and three-decimal currencies (KWD, BHD…) keep their third decimal.
currency must be a three-letter ISO-4217 code. return_url must be an http or https URL.
Create Checkout Session¶
Creates a secure checkout session (Server-to-Server flow). Returns the session ID and the custom checkout URL to redirect the buyer to.
Request Body¶
| Field | Type | Required | Description |
|---|---|---|---|
| amount | string | Yes | Total amount to charge in major units, greater than 0 (e.g., 99.99) |
| currency | string | Yes | ISO-4217 currency code, e.g. USD |
| return_url | string | Yes | URL to redirect after payment completion |
| description | string | No | A description of the payment |
| items | array[string] | No | Name(s) of product(s) or service(s). A comma-separated string is also accepted |
| mode | string | No | Checkout mode: payment or subscription. Default: payment |
| price_id | string | Yes for subscriptions | Price ID used to create the subscription |
| customer_id | string | Yes for subscriptions | Customer ID used to create the subscription |
The merchant must have an approved payment account for the session’s mode (test or live). Otherwise the request fails with a 409 instead of charging the platform account.
Request Example¶
import requests
response = requests.post(
'https://api.4geeks.io/v1/payments/session',
headers={'X-Api-Key': 'sk_test_your_api_key_here'},
json={
'amount': '99.99',
'currency': 'USD',
'description': 'Premium subscription',
'items': ['Premium Plan'],
'return_url': 'https://yoursite.com/success'
}
)
print(response.json())
const response = await fetch('https://api.4geeks.io/v1/payments/session', {
method: 'POST',
headers: {
'X-Api-Key': 'sk_test_your_api_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: '99.99',
currency: 'USD',
description: 'Premium subscription',
items: ['Premium Plan'],
return_url: 'https://yoursite.com/success'
})
});
const data = await response.json();
console.log(data);
Response (201 Created)¶
| Field | Type | Description |
|---|---|---|
| session_id | string | The unique session ID to pass to the frontend |
| checkout_url | string | The URL you should redirect your user to |
Response Example¶
{
"session_id": "sess_1a2b3c4d5e6f",
"checkout_url": "https://checkout.4geeks.io/checkout?session_id=sess_1a2b3c4d5e6f"
}
Get Checkout Session¶
Retrieves safe details of a checkout session for the frontend to display. No API key required because the session ID acts as a secure token for the buyer.
Path Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
| session_id | string | Yes | The checkout session ID |
Request Example¶
Response (200 OK)¶
| Field | Type | Description |
|---|---|---|
| id | string | Session ID |
| amount | string | Amount in major units |
| currency | string | Currency code |
| description | string | Payment description |
| items | array[string] | Product/service names |
| mode | string | Checkout mode |
| price_id | string | Price ID for subscriptions |
| customer_id | string | Customer ID |
| return_url | string | Redirect URL |
| status | string | pending, completed, failed or expired |
| gateway_account_id | string | Connected account that receives the funds |
| merchant_name | string | Merchant name from the API keys table |
| is_test | boolean | Whether the session belongs to the test environment |
Response Example¶
{
"id": "sess_1a2b3c4d5e6f",
"amount": "99.99",
"currency": "USD",
"description": "Premium subscription",
"items": ["Premium Plan"],
"mode": "payment",
"price_id": null,
"customer_id": null,
"return_url": "https://yoursite.com/success",
"status": "pending",
"gateway_account_id": "acct_abc123",
"merchant_name": "My Store",
"is_test": false
}
Get Checkout Session Status¶
Minimal status for checkout polling. It returns no payment details and is never cached, so the checkout page can poll it while the buyer confirms the payment.
Path Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
| session_id | string | Yes | The checkout session ID |
Request Example¶
Response (200 OK)¶
| Field | Type | Description |
|---|---|---|
| session_id | string | Session ID |
| status | string | pending, completed, failed or expired |
| is_test | boolean | Whether the session belongs to the test environment |
Response Example¶
Create Payment Intent¶
Creates the PaymentIntent a branded checkout confirms, using a predefined session ID. No API key required because the session is already securely bound to a merchant.
The call is idempotent per session: a retry (double click, network retry) returns the same PaymentIntent instead of charging the buyer twice. A session that is no longer pending, or that belongs to a subscription, is rejected with 409.
Request Body¶
| Field | Type | Required | Description |
|---|---|---|---|
| session_id | string | Yes | The secure checkout session ID |
Request Example¶
Response (201 Created)¶
| Field | Type | Description |
|---|---|---|
| client_secret | string | PaymentIntent client_secret. Pass this to the client SDK on the frontend |
| payment_intent_id | string | The unique PaymentIntent ID |
| amount | string | Amount in major units |
| currency | string | Currency code |
Response Example¶
{
"client_secret": "pi_1a2b3c_secret_4d5e6f",
"payment_intent_id": "pi_1a2b3c4d5e6f",
"amount": "99.99",
"currency": "USD"
}
Keep the payment intent ID
Store payment_intent_id: it is the identifier you pass to the refund endpoint when the session was created with this flow.
Create Subscription (via Payment Intent)¶
Creates a subscription for a secure mode: subscription session and returns the client secret of the first invoice’s PaymentIntent.
Request Body¶
| Field | Type | Required | Description |
|---|---|---|---|
| session_id | string | Yes | The secure checkout session ID |
Request Example¶
Response (201 Created)¶
Same schema as Create Payment Intent.
A subscription whose first invoice needs no payment (for example, a fully discounted period) answers 409 instead of returning an empty client secret.
Create Payment¶
Creates a hosted checkout session for the legacy Server-to-Server flow.
Request Body¶
| Field | Type | Required | Description |
|---|---|---|---|
| amount | string | Yes | Total amount to charge in major units, greater than 0 |
| description | string | Yes | A description of the payment or service |
| currency | string | Yes | ISO-4217 currency code, e.g. USD, CAD, CRC, EUR |
| items | array[string] | Yes | Name(s) of product(s) or service(s) |
| return_url | string | Yes | URL to redirect after payment completion |
Request Example¶
import requests
response = requests.post(
'https://api.4geeks.io/v1/payments',
headers={'X-Api-Key': 'sk_test_your_api_key_here'},
json={
'amount': '49.99',
'description': 'Monthly plan',
'currency': 'USD',
'items': ['Monthly Subscription'],
'return_url': 'https://yoursite.com/thank-you'
}
)
print(response.json())
const response = await fetch('https://api.4geeks.io/v1/payments', {
method: 'POST',
headers: {
'X-Api-Key': 'sk_test_your_api_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: '49.99',
description: 'Monthly plan',
currency: 'USD',
items: ['Monthly Subscription'],
return_url: 'https://yoursite.com/thank-you'
})
});
const data = await response.json();
console.log(data);
Response (202 Accepted)¶
| Field | Type | Description |
|---|---|---|
| code | integer | HTTP status code (default: 202) |
| title | string | Response title (default: “Action required”) |
| content | string | Response message (default: “Show the checkout page to complete payment.”) |
| data | object | Payment data object |
| data.id_payment | string | Payment ID |
| data.redirect | string | Redirect URL for checkout |
The amount is charged on the merchant’s connected account. The return_url keeps its query string and receives an extra id_payment parameter.
Response Example¶
{
"code": 202,
"title": "Action required",
"content": "Show the checkout page to complete payment.",
"data": {
"id_payment": "pay_7g8h9i0j1k2l",
"redirect": "https://checkout.4geeks.io/pay/pay_7g8h9i0j1k2l"
}
}
List Payments¶
Lists the caller’s checkout sessions, newest first. Results are always scoped to the merchant that owns the API key, and to the environment of the key (test or live).
Query Parameters¶
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| status | string | No | - | Filter by pending, completed, failed or expired |
| limit | integer | No | 10 | Page size, 1–100 |
| offset | integer | No | 0 | Number of results to skip |
Request Example¶
Response (200 OK)¶
| Field | Type | Description |
|---|---|---|
| items | array[object] | Checkout sessions, same schema as Get Checkout Session |
| total_items | integer | Total number of sessions |
| limit | integer | Requested page size |
| offset | integer | Requested offset |
| has_more | boolean | Whether another page exists |
Response Example¶
{
"items": [
{
"id": "sess_1a2b3c4d5e6f",
"amount": "99.99",
"currency": "USD",
"description": "Premium subscription",
"items": ["Premium Plan"],
"mode": "payment",
"price_id": null,
"customer_id": null,
"return_url": "https://yoursite.com/success",
"status": "completed",
"gateway_account_id": "acct_abc123",
"merchant_name": "My Store",
"is_test": true
}
],
"total_items": 1,
"limit": 5,
"offset": 0,
"has_more": false
}
Get Payment¶
Retrieves a hosted-checkout payment by its gateway ID. The payment must belong to the merchant that owns the API key; anything else answers 404.
Path Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
| payment_id | string | Yes | The hosted-checkout payment ID |
Request Example¶
Response (200 OK)¶
| Field | Type | Description |
|---|---|---|
| id | string | The unique payment ID |
| amount | string | Amount in major units |
| currency | string | Currency code |
| description | string | Payment description |
| status | string | pending, completed or failed |
| items | array[string] | Product/service names |
| customer_email | string | Customer email |
| return_url | string | Redirect URL, without the internal id_payment parameter |
| payment_method | string | Payment method (default: “card”) |
| card_last4 | string | Last 4 digits of card |
| card_brand | string | Card brand |
| created_at | string (datetime) | Creation timestamp |
| updated_at | string (datetime) | Update timestamp |
| test | boolean | Whether the payment was created with a test key |
Response Example¶
{
"id": "pay_7g8h9i0j1k2l",
"amount": "49.99",
"currency": "USD",
"description": "Monthly plan",
"status": "completed",
"items": ["Monthly Subscription"],
"customer_email": "customer@example.com",
"return_url": "https://yoursite.com/thank-you",
"payment_method": "card",
"card_last4": "4242",
"card_brand": "visa",
"created_at": "2026-04-25T10:30:00Z",
"updated_at": "2026-04-25T10:31:00Z",
"test": true
}
Refund a Payment¶
Refunds are documented separately: see the Refunds API for full and partial refunds, Connect behavior, and refund listings.
Webhook¶
Point the gateway’s webhook at this endpoint so the session status stays in sync even if the buyer closes the checkout page before it redirects.
- The API verifies the signature of every delivery with the live and test signing secrets (
PAYMENT_GATEWAY_WEBHOOK_SECRET,PAYMENT_GATEWAY_TEST_WEBHOOK_SECRET). Unsigned, tampered or replayed deliveries (older than five minutes) are rejected with400. - Payloads are capped at 1 MB.
- A successful payment marks the session
completed; an expired checkout marks itexpired; a declined attempt leaves the sessionpendingso the buyer can retry with another card. - The update is idempotent: a retried delivery never changes a session twice, and any other event is acknowledged with
200so the gateway stops retrying. - Accounting (balances and transactions) is handled by the platform, not by this endpoint.
No API key is required. Configure the endpoint and its signing secret in the console; requests that are not validly signed are refused.
Errors¶
| Status | Meaning |
|---|---|
| 401 | Missing or invalid API key |
| 404 | Session or payment not found (also when it belongs to another merchant) |
| 409 | Business rule: session no longer payable, wrong mode, or the merchant has no approved payment account (gateway_account_not_configured, gateway_account_not_approved, session_not_pending, wrong_session_mode, subscription_no_payment_required) |
| 422 | Validation error (amount, currency, URL, mode, missing subscription fields) |
| 429 | Rate limit exceeded |
| 502 / 503 | The gateway rejected the request or is not configured |
Error Response (422 Validation Error)¶
All endpoints may return a 422 status when validation fails.
{
"detail": [
{
"loc": ["body", "amount"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
Business errors use the same envelope as the rest of the API:
{
"detail": {
"code": "session_not_pending",
"message": "This checkout session is no longer payable."
}
}
Still have questions? Ask on Discord or explore the tutorials.