---
title: "Direct recharge — Anis Developers"
description: "Choose a direct recharge product, collect recipient details, quote and follow delivery or refund."
url: https://developers.anis.ly/docs/direct-recharge/
language: en
---

> Every page of this documentation: https://developers.anis.ly/llms.txt

# Direct recharge

A direct recharge product defines the recipient service. A direct recharge is your purchase of that product. You pay LYD from your wallet; Anis staff deliver the payout to the recipient. This can take time. It is not an API for moving money between Anis wallets.

## Choose a product

Sign `GET /v1/wallets/{walletId}/direct-recharge-products?type=external`. The enum retains `billing`, but billing products are not available yet; they cannot be quoted or purchased. Reads need `direct_recharges:read`, also included in `direct_recharges:create`. Catalogue, location and quote answers are unsigned. Pass `nextCursor` unchanged as `cursor`; the last page has `nextCursor: null`.

```json
{
  "items": [
    {
      "id": "e7c2a9d4-1b3f-4a85-9c60-8d2e4f7b1a93",
      "title": "فودافون كاش مصر",
      "description": "تحويل إلى محافظ فودافون كاش",
      "type": "external",
      "category": {
        "id": "4b9e...",
        "name": "مصر",
        "icon": "https://cdn.anis.ly/circle/eg.png"
      },
      "payoutCurrency": {
        "name": "جنيه مصري"
      },
      "available": true,
      "limits": {
        "minimumTotal": {
          "amount": "10.000",
          "currency": "LYD"
        },
        "maximumTotal": {
          "amount": "5000.000",
          "currency": "LYD"
        },
        "minimumPayout": "50.000",
        "maximumPayout": "30000.000"
      }
    }
  ],
  "nextCursor": null
}
```

Pricing and limits depend on this wallet’s tier. `available: false` includes a missing wallet price or a Fazaa-only price; partners cannot buy through Fazaa. Limits may be absent without a matching tier. Payout currency is a display name, not an ISO code: do not turn it into Money or invent a code.

## Collect fields and locations

Read `GET /v1/wallets/{walletId}/direct-recharge-products/{productId}` for the product and its ordered fields.

```json
{
  "id": "e7c2a9d4-1b3f-4a85-9c60-8d2e4f7b1a93",
  "title": "فودافون كاش مصر",
  "type": "external",
  "payoutCurrency": {
    "name": "جنيه مصري"
  },
  "available": true,
  "limits": {
    "minimumTotal": {
      "amount": "10.000",
      "currency": "LYD"
    },
    "maximumTotal": {
      "amount": "5000.000",
      "currency": "LYD"
    },
    "minimumPayout": "50.000",
    "maximumPayout": "30000.000"
  },
  "recipientFields": [
    {
      "key": "recipient_number",
      "name": {
        "ar": "رقم المحفظة",
        "en": "Wallet number"
      },
      "kind": "text",
      "for": "recipient",
      "order": 1,
      "validation": "^01[0-9]{9}$",
      "validationMessage": {
        "ar": "رقم غير صحيح",
        "en": "Invalid number"
      }
    },
    {
      "key": "recipient_name",
      "name": {
        "ar": "اسم المستلم",
        "en": "Recipient name"
      },
      "kind": "text",
      "for": "recipient",
      "order": 2
    },
    {
      "key": "recipient_location",
      "name": {
        "ar": "المدينة",
        "en": "City"
      },
      "kind": "list",
      "for": "recipient",
      "order": 3
    }
  ]
}
```

| Field | What to do |
| --- | --- |
| `key` | Preserve it in each submitted key/value entry |
| `name` | Show the Arabic or English label |
| `kind` | `text` is text; `list` offers choices; `note` is display guidance |
| `for` | `recipient` or `sender`, as supplied |
| `order` | Show fields in this order |
| `validation`, `validationMessage` | Apply the supplied rule and localized message when present |

For location choices use `GET /v1/wallets/{walletId}/direct-recharge-products/{productId}/locations?search=القاهرة`. Search is optional; continue with `cursor`.

```json
{
  "items": [
    {
      "id": "1a2b...",
      "name": "القاهرة"
    },
    {
      "id": "3c4d...",
      "name": "الإسكندرية"
    }
  ],
  "nextCursor": null
}
```

Send the selected location id as its field’s value, not the display name.

## Quote both sides

`GET /v1/wallets/{walletId}/direct-recharge-products/{productId}/quote?amount=1500.000&amountIn=fx` checks the price without reserving money. `fx` means input in payout currency; `lyd` means input in dinars. The answer always shows both sides.

```json
{
  "productId": "e7c2a9d4-1b3f-4a85-9c60-8d2e4f7b1a93",
  "payout": {
    "amount": "1500.000",
    "currency": {
      "name": "جنيه مصري"
    }
  },
  "exchangeRate": "0.155000",
  "price": {
    "amount": "232.500",
    "currency": "LYD"
  },
  "commission": {
    "amount": "5.000",
    "currency": "LYD"
  },
  "total": {
    "amount": "237.500",
    "currency": "LYD"
  }
}
```

`total` is the wallet cost. Applicable location and holder adjustments are inside `price`; commission is separate. Amounts are decimal strings, never floating-point JSON numbers. The exchange rate is also a decimal string, illustrated with six decimals.

## Request a direct recharge

Save a UUID and the exact body before `POST /v1/wallets/{walletId}/direct-recharges`. Requires `direct_recharges:create`. Sign the body digest, fresh nonce and `Idempotency-Key`; the key is the operation id. Send `payoutAmount` and quoted `expectedTotal`. Do not send `price`, `priceWithCommission` or `extraFee`.

```json
{
  "productId": "e7c2a9d4-1b3f-4a85-9c60-8d2e4f7b1a93",
  "payoutAmount": "1500.000",
  "expectedTotal": {
    "amount": "237.500",
    "currency": "LYD"
  },
  "recipient": [
    {
      "key": "recipient_number",
      "value": "01012345678"
    },
    {
      "key": "recipient_name",
      "value": "Ahmed Ali"
    },
    {
      "key": "recipient_location",
      "value": "1a2b..."
    }
  ],
  "note": "Family support",
  "externalReference": "POS-77-TR-551"
}
```

Verify the signed `202`. An accepted direct recharge can answer with `Location: /v1/direct-recharges/{operationId}` and:

```json
{
  "operationId": "7d4e1a90-5b2c-4e7f-8a13-0c9d6e2b4f81",
  "operationType": "directRecharge",
  "status": "processing"
}
```

The balance check is exact up to 999,999.999 LYD. This bound applies to the LYD wallet balance, not to the named payout currency.

Before recording the purchase, Anis checks the wallet balance; insufficient funds return `insufficient_balance`, `409`. A later competing debit may still cause `paymentFailed`.

A missing read is not proof of absence. Retry the saved id/body. After a duplicate, `202 processing` allows projection lag; if the application-scoped read is still missing after 60 seconds since admission, the retry closes with `idempotency_conflict`, `409`. That code does not prove nothing was charged: the first request may already have been accepted, so read the operation before any new attempt.

Payment and delivery continue. If the answer is lost or the outcome unknown, read the same operation or retry its exact saved body with the same key and a fresh signature. Never use a new id to work around uncertainty.

## Read progress and refunds

Sign `GET /v1/direct-recharges/{operationId}` with read or create permission and verify the signed answer. Another application’s direct recharge looks like a missing one.

```json
{
  "commission": {
    "amount": "5.000",
    "currency": "LYD"
  },
  "completedAt": "2026-10-20T10:00:00Z",
  "exchangeRate": "0.155000",
  "externalReference": "POS-77-TR-551",
  "note": "Family support",
  "number": 30412,
  "operationId": "7d4e1a90-5b2c-4e7f-8a13-0c9d6e2b4f81",
  "payout": {
    "amount": "1500.000",
    "currency": {
      "name": "جنيه مصري"
    }
  },
  "price": {
    "amount": "232.500",
    "currency": "LYD"
  },
  "product": {
    "id": "e7c2a9d4-1b3f-4a85-9c60-8d2e4f7b1a93",
    "title": "فودافون كاش مصر"
  },
  "proofUrl": "https://cdn.anis.ly/circle/proofs/example.png",
  "recipient": [
    {
      "key": "recipient_name",
      "name": {
        "ar": "اسم المستلم",
        "en": "Recipient name"
      },
      "value": "Ahmed Ali"
    },
    {
      "key": "recipient_number",
      "name": {
        "ar": "رقم المحفظة",
        "en": "Wallet number"
      },
      "value": "01012345678"
    }
  ],
  "requestedAt": "2026-10-20T09:00:00Z",
  "status": "completed",
  "total": {
    "amount": "237.500",
    "currency": "LYD"
  },
  "walletId": "71825266-7182-4182-8182-718252667182"
}
```

| `status` | Meaning |
| --- | --- |
| `awaitingPayment` | Recorded; money is being taken |
| `paymentFailed` | Payment refused; nothing taken (final) |
| `pending` | Paid; waiting for an officer |
| `inProgress` | An officer is working on it |
| `suspendedDataError` | Correct recipient data; reason explains |
| `completed` | Delivered (final) |
| `rejectedAwaitingRefund` | Rejected; refund on its way |
| `rejectedWithRefund` | Rejected; money returned (final) |

`reason` appears for suspended and both rejected states. `proofUrl`, when supplied, points to recorded delivery proof. Partial refunds occur only if partial refunds are enabled for the product; all current new direct recharges, including those made by apps, are ineligible. A settled partial refund retains the app’s merged status and adds this block; it introduces no new status.

```json
{
  "partialRefund": {
    "refundedPayout": "500.000",
    "refundedTotal": {
      "amount": "77.500",
      "currency": "LYD"
    },
    "remainingPayout": "1000.000",
    "remainingTotal": {
      "amount": "160.000",
      "currency": "LYD"
    }
  }
}
```

`refundedTotal` is money actually returned; `remainingTotal` is the remaining wallet cost. Payout amounts are strings in the display currency. Do not call a requested but unsettled refund completed. Rejected notifications follow full refund settlement; partial-refund notifications follow partial settlement.

A read can also answer a refusal that was recorded for the create under this id, with `Idempotency-Replayed: true`: `409` (`insufficient_balance`, `price_changed` or `direct_recharge_product_unavailable`) or `422` (`amount_out_of_range`, or recipient data that was refused). Nothing was charged. On a read, `idempotency_conflict` (`409`) means no direct recharge of your application exists under this id; use a new `Idempotency-Key` only for a genuinely new operation.

## Find my operations

Sign `GET /v1/wallets/{walletId}/direct-recharges` with `direct_recharges:read` or `direct_recharges:create`. The answer is unsigned. Only this application’s operations in the selected authorized wallet are returned, newest first. Optional filters, which combine: `status` (`awaitingPayment`, `paymentFailed`, `pending`, `inProgress`, `suspendedDataError`, `completed`, `rejectedAwaitingRefund` or `rejectedWithRefund`), `productId` (a UUID), `from` and `to` (UTC), `cursor` and `pageSize`. Each window is at most 31 days; omitted dates mean the last 31 days. The page size defaults to 50 and is at most 100. Pass `nextCursor` unchanged with the same filters and `pageSize` until it is `null`. An empty result has `items: []` and `nextCursor: null`. After `validation_failed` (`422`), correct the filters or cursor; do not repeat a purchase to recover a list. Statuses match the detail read; a partial refund keeps its merged status, and `hasPartialRefund` reports it separately. Recipient details and proof remain on the signed detail read.

```json
{
  "items": [
    {
      "operationId": "7d4e1a90-5b2c-4e7f-8a13-0c9d6e2b4f81",
      "status": "completed",
      "number": 30412,
      "product": {
        "id": "e7c2a9d4-1b3f-4a85-9c60-8d2e4f7b1a93",
        "title": "فودافون كاش مصر"
      },
      "payout": {
        "amount": "1500.000",
        "currency": {
          "name": "جنيه مصري"
        }
      },
      "total": {
        "amount": "237.500",
        "currency": "LYD"
      },
      "externalReference": "POS-77-TR-551",
      "requestedAt": "2026-10-20T09:00:00Z",
      "completedAt": "2026-10-20T09:14:00Z",
      "hasPartialRefund": false
    }
  ],
  "nextCursor": null
}
```

## Correct a suspended recipient

Only while `status` is `suspendedDataError`, send `PATCH /v1/direct-recharges/{operationId}/recipient`. Requires create permission, signed digest and a fresh nonce. This is a signed change, not a new purchase; do not send Idempotency-Key. Send the complete corrected recipient list. It replaces the saved list. Include every required field and any optional values you want to retain, including `recipient_location`.

```json
{
  "recipient": [
    {
      "key": "recipient_number",
      "value": "01098765432"
    },
    {
      "key": "recipient_name",
      "value": "Ahmed Ali Hassan"
    },
    {
      "key": "recipient_location",
      "value": "1a2b..."
    }
  ]
}
```

Verify the signed answer. A successful correction returns signed `202 Accepted` with `Location: /v1/direct-recharges/{operationId}` and the standard processing acknowledgement: only `operationId`, `operationType: "directRecharge"` and `status: "processing"`. It carries no direct recharge or order body. Follow `Location` to read the direct recharge or wait for a `directRecharge.*` webhook. Acceptance does not mean the corrected state is visible yet. Read before sending again: an already-applied correction can be refused as `direct_recharge_not_suspended`. The response headers and processing acknowledgement:

```http
HTTP/1.1 202 Accepted
Location: /v1/direct-recharges/7d4e1a90-5b2c-4e7f-8a13-0c9d6e2b4f81
```

```json
{
  "operationId": "7d4e1a90-5b2c-4e7f-8a13-0c9d6e2b4f81",
  "operationType": "directRecharge",
  "status": "processing"
}
```

Validate the supplied fields against the product’s rules. A recipient validation problem may contain this fragment:

```json
{"extensions":{"fields":{"recipient_number":"Invalid number"}}}
```

`direct_recharge_not_suspended` is `409`; `validation_failed` is `422`; an unknown or foreign direct recharge is `resource_not_found`, `404`.

## Refusals before purchase

| Code | HTTP | What it means |
| --- | --- | --- |
| `price_changed` | `409` | Quote moved; quote and confirm again |
| `direct_recharge_product_unavailable` | `409` | This wallet cannot buy it |
| `amount_out_of_range` | `422` | Payout or total outside limits |
| `validation_failed` | `422` | Correct fields in extensions.fields |
| `purchase_not_allowed` | `403` | You cannot buy your own product |
| `insufficient_balance` | `409` | Insufficient funds |
| `spending_limit_exceeded` | `429` | Shared application cap reached |
| `idempotency_conflict` | `409` | Changed saved intent, or a repeated create still unknown after the 60-second lag window; the first request may already be charged, so read it first |

Purchases share the daily cap and purchase request limit with card orders and meter charges. After a definitive refusal closes the operation, a corrected purchase needs a new id. Admission, signing or transport failure does not prove an earlier attempt failed.

`idempotency_conflict` and `replay_detected` do not say the first request failed: it may already have been accepted and charged, so never assume nothing was charged. Read the original operation with `GET /v1/direct-recharges/{operationId}` before any new attempt. To repeat the same intent after `replay_detected`, keep the `Idempotency-Key` and send a fresh nonce and signature. After `idempotency_conflict`, reuse the key only for identical intent and use a new `Idempotency-Key` only for a genuinely new operation. `insufficient_balance`, `price_changed` and the unavailable-product refusals happen before money moves: nothing was charged.

[Receive outcomes through webhooks](https://developers.anis.ly/docs/webhooks.md) and [verify each notification](https://developers.anis.ly/docs/verifying-webhooks.md) before processing it.

[Products](https://developers.anis.ly/reference/get-direct-recharge-products.md) · [Product](https://developers.anis.ly/reference/read-direct-recharge-product.md) · [Locations](https://developers.anis.ly/reference/get-direct-recharge-product-locations.md) · [Quote](https://developers.anis.ly/reference/direct-recharge-quote.md) · [Create](https://developers.anis.ly/reference/create-direct-recharge.md) · [Read](https://developers.anis.ly/reference/read-direct-recharge.md) · [Recipient](https://developers.anis.ly/reference/update-direct-recharge-recipient.md) · [Find my operations](https://developers.anis.ly/reference/get-direct-recharges.md)
