# Wallet — PapaProxy.net API reference

Page: https://papaproxy.net/developers/reference/wallet.php
Last updated: November 9, 2026
Base URL: `https://papaproxy.net/account_new`. Authentication and conventions: https://papaproxy.net/developers/reference/

### Get wallet balance

`GET /api/v1/customer/wallet/balance`

- Access: API key required · scope `wallet:read`

Your prepaid wallet balance, calculated from the transaction ledger. Accounts that have never topped up report `balance_cents=0`.

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/wallet/balance" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/customer/wallet/balance",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/wallet/balance", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/wallet/balance');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/customer/wallet/balance", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — Current wallet balance.

Example response:

```json
{
  "wallet_id": "d7fa9fe6-fd70-582d-aa84-e52c5068ca35",
  "owner_type": "customer",
  "owner_id": "8218fbc6-d67b-52fd-a639-cca399215d31",
  "balance_cents": 1997,
  "currency": "USD",
  "has_wallet": true
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `wallet_id` | string (uuid), nullable | no | ID of your wallet. |
| `owner_type` | string | yes |  |
| `owner_id` | string (uuid) | yes |  |
| `balance_cents` | integer | yes | Available balance in integer cents |
| `currency` | string | no | Currency code (`USD`). · Min length: 3 · Max length: 3 · Default: `"USD"` |
| `has_wallet` | boolean | no | False if the account has never topped up; `balance_cents` is 0 in that case. · Default: `true` |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `wallet:read`.

  Example:

  ```json
  {"detail": "API key is missing required scope: wallet:read"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### List wallet transactions

`GET /api/v1/customer/wallet/ledger`

- Access: API key required · scope `wallet:read`

List wallet transactions (paginated, newest first).

Entries linked to a specific service carry its `service_id`.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `offset (query)` | integer | no | Number of items to skip. · Minimum: 0 · Default: `0` |
| `limit (query)` | integer | no | Page size. · Minimum: 1 · Maximum: 200 · Default: `50` |

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/wallet/ledger" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/customer/wallet/ledger",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/wallet/ledger", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/wallet/ledger');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/customer/wallet/ledger", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — Paginated list of ledger entries.

Example response:

```json
{
  "entries": [
    {
      "id": "d4ac48f1-e447-50a4-9b61-a95ea7505be0",
      "wallet_id": "d7fa9fe6-fd70-582d-aa84-e52c5068ca35",
      "idempotency_key": "addon_refund:fc9f2062-7726-5122-ab7f-d6a7814ed548#s1",
      "provider": null,
      "provider_tx_id": null,
      "amount_cents": 3,
      "balance_after_cents": 1997,
      "entry_type": "refund",
      "status": "completed",
      "description": "Addon refund: Extra connections (30 unused days) for Datacenter Proxy US 500 IPs",
      "service_id": "cb532178-5b03-506a-84ea-0230ff28576b",
      "usage_requests": null,
      "usage_bytes": null,
      "created_at": "2026-11-07T14:30:45.447818Z"
    },
    {
      "id": "872ef948-7db3-5d67-9755-cc0f965b2b7a",
      "wallet_id": "d7fa9fe6-fd70-582d-aa84-e52c5068ca35",
      "idempotency_key": "addon_purchase:capture-d576b57ad59e",
      "provider": null,
      "provider_tx_id": null,
      "amount_cents": -3,
      "balance_after_cents": 1994,
      "entry_type": "debit",
      "status": "completed",
      "description": "Addon purchase: Extra connections for Datacenter Proxy US 500 IPs",
      "service_id": "cb532178-5b03-506a-84ea-0230ff28576b",
      "usage_requests": null,
      "usage_bytes": null,
      "created_at": "2026-11-07T14:30:43.459177Z"
    }
  ],
  "total": 26,
  "offset": 0,
  "limit": 3
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `entries` | array of object | yes |  |
| `entries[].id` | string (uuid) | yes | Unique ID. |
| `entries[].wallet_id` | string (uuid) | yes | ID of your wallet. |
| `entries[].idempotency_key` | string | yes |  |
| `entries[].provider` | string, nullable | yes | Payment processor or source of the transaction. |
| `entries[].provider_tx_id` | string, nullable | yes | Transaction ID at the payment processor. |
| `entries[].amount_cents` | integer | yes | Amount in integer cents |
| `entries[].balance_after_cents` | integer | yes | Running balance after this entry |
| `entries[].entry_type` | string | yes |  |
| `entries[].status` | string | yes |  |
| `entries[].description` | string, nullable | yes |  |
| `entries[].service_id` | string (uuid), nullable | yes | ID of the service. |
| `entries[].usage_requests` | integer, nullable | yes |  |
| `entries[].usage_bytes` | integer, nullable | yes |  |
| `entries[].created_at` | string (date-time) | yes | When it was created (ISO 8601, UTC). |
| `total` | integer | yes | Total number of items across all pages. |
| `offset` | integer | no | Number of items skipped. · Default: `0` |
| `limit` | integer | no | Page size. · Default: `50` |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `wallet:read`.

  Example:

  ```json
  {"detail": "API key is missing required scope: wallet:read"}
  ```

- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### Top up your wallet

`POST /api/v1/customer/wallet/deposit/init`

- Access: API key required · scope `wallet:deposit`

Start a wallet top-up and get a checkout URL. The wallet is credited after the payment processor confirms the payment — check the transaction list to see the credit. Pass `idempotency_key` to make retries safe.

If the selected payment method requires identity verification that hasn't been completed, the response is HTTP 403 with `detail.error = "kyc_required"`.

Request body (JSON):

| Field | Type | Required | Description |
|---|---|---|---|
| `gateway` | string | yes | Payment gateway: stripe \| heleket \| freekassa \| onepayment · Min length: 1 · Max length: 64 |
| `amount_cents` | integer | yes | Amount in integer cents |
| `idempotency_key` | string, nullable | no | Optional client-supplied idempotency key · Max length: 255 |
| `currency` | string | no | Currency code (`USD`). · Min length: 3 · Max length: 3 · Default: `"USD"` |
| `order_metadata` | object, nullable | no | Optional order details to attach to the payment. |
| `return_url` | string, nullable | no | Relative path to return to after a canceled or failed payment (for example `/wallet`). Must start with `/`. · Max length: 2048 |
| `success_url` | string, nullable | no | Relative path to return to after a successful payment. Must start with `/`. · Max length: 2048 |
| `save_card` | boolean | no | Save the card for automatic top-ups (card payments via Stripe only). · Default: `false` |

Request examples:

cURL:

```bash
curl -X POST "https://papaproxy.net/account_new/api/v1/customer/wallet/deposit/init" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount_cents": 5000, "gateway": "heleket", "idempotency_key": "deposit-2026-001"}'
```

Python:

```python
import os

import requests

resp = requests.post(
    "https://papaproxy.net/account_new/api/v1/customer/wallet/deposit/init",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    json={
        "amount_cents": 5000,
        "gateway": "heleket",
        "idempotency_key": "deposit-2026-001"
    },
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/wallet/deposit/init", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "amount_cents": 5000,
    "gateway": "heleket",
    "idempotency_key": "deposit-2026-001"
  }),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/wallet/deposit/init');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount_cents' => 5000,
        'gateway' => 'heleket',
        'idempotency_key' => 'deposit-2026-001'
    ]),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	req, err := http.NewRequest("POST", "https://papaproxy.net/account_new/api/v1/customer/wallet/deposit/init", strings.NewReader(`{"amount_cents": 5000, "gateway": "heleket", "idempotency_key": "deposit-2026-001"}`))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — Top-up checkout details.

Example response:

```json
{
  "payment_url": "https://pay.example.com/invoice/675388be-0c31-539b-8e81-0272df8d2f24",
  "our_order_id": "HLK-8F2A61C04D9E7B35",
  "amount_cents": 5000,
  "currency": "USD",
  "provider_tx_id": null,
  "checkout_kind": "redirect",
  "allowed_payment_methods": null,
  "paypal_remaining_cents": null
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `payment_url` | string | yes |  |
| `our_order_id` | string | yes |  |
| `amount_cents` | integer | yes |  |
| `currency` | string | no | Currency code (`USD`). · Default: `"USD"` |
| `provider_tx_id` | string, nullable | no |  |
| `checkout_kind` | string | no | Default: `"redirect"` |
| `allowed_payment_methods` | array of string, nullable | no | Payment methods allowed for this top-up, when restricted (for example `["card"]`). Null means no restriction. |
| `paypal_remaining_cents` | integer, nullable | no | Remaining PayPal limit for this account, in cents. Null when there is no limit. |

- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `wallet:deposit`, or the payment method requires identity verification (`detail.error = "kyc_required"`).
- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```


### Download an invoice (PDF)

`GET /api/v1/customer/invoices/{invoice_id}/pdf`

- Access: API key required · scope `wallet:read`

Download the PDF of one of your invoices.

Parameters:

| Field | Type | Required | Description |
|---|---|---|---|
| `invoice_id (path)` | string | yes | ID of the invoice. |

Request examples:

cURL:

```bash
curl -X GET "https://papaproxy.net/account_new/api/v1/customer/invoices/string/pdf" \
  -H "Authorization: Bearer $PAPAPROXY_API_KEY"
```

Python:

```python
import os

import requests

resp = requests.get(
    "https://papaproxy.net/account_new/api/v1/customer/invoices/string/pdf",
    headers={"Authorization": f"Bearer {os.environ['PAPAPROXY_API_KEY']}"},
    timeout=30,
)
resp.raise_for_status()
print(resp.json())
```

Node.js:

```javascript
const resp = await fetch("https://papaproxy.net/account_new/api/v1/customer/invoices/string/pdf", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PAPAPROXY_API_KEY}`,
  },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
```

PHP:

```php
<?php
$ch = curl_init('https://papaproxy.net/account_new/api/v1/customer/invoices/string/pdf');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAPAPROXY_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
var_dump($status, json_decode($response, true));
```

Go:

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://papaproxy.net/account_new/api/v1/customer/invoices/string/pdf", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PAPAPROXY_API_KEY"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}
```

Responses:

- `200` — The invoice PDF.
- `401` — The API key is missing, malformed, revoked, or expired, or the account is inactive.

  Example:

  ```json
  {"detail": "Invalid or revoked API key."}
  ```

- `403` — The API key lacks scope `wallet:read`.

  Example:

  ```json
  {"detail": "API key is missing required scope: wallet:read"}
  ```

- `404` — Invoice not found, or it doesn't belong to your account.
- `422` — Validation error: a field is missing or has the wrong type or value.

  Example:

  ```json
  {"detail": "Validation error"}
  ```

- `429` — Too many requests: the per-account limit is 60 requests per minute, shared by all keys and the MCP server. Retry with exponential backoff.

  Example:

  ```json
  {"detail": "Too Many Requests"}
  ```

