Build payments into your product with TPE virtual. Start integrating →
Logo

Balance

Read your MyTPE balance, top it up, and send funds to your customers one-by-one or in bulk.

Every trader has a single MyTPE balance in DZD — a prepaid, closed-loop merchant account used to fund customer refunds, payouts, and internal transfers. The external API lets you:

  • Read the current balance ("sold").
  • Initiate a top-up through SATIM (returns a hosted form URL you redirect your user to).
  • Submit an offline top-up (bank transfer receipt or cheque photo) for admin approval.
  • Send funds to a payer by email, phone, or user id — one at a time or in a batch.

Not a regulated e-wallet

The MyTPE balance is an internal merchant credit used only inside the MyTPE ecosystem. It cannot be cashed out to third parties directly and is not a regulated payment account. Some legacy fields and error codes still use the prefix WALLET_* — treat them as opaque identifiers.

v1 rules

  • Only traders can hold or fund a balance. API keys already belong to trader accounts, so the caller of these endpoints is always the account holder.
  • Trader → trader transfers are disabled. Recipients must be non-trader users (payer accounts).
  • Every write endpoint (recharge, transfer, transfer/bulk) requires an X-Idempotency-Key header — retries with the same key are safe no-ops.

Endpoints

MethodPathDescription
GET/walletRetrieve the caller's balance and status.
POST/wallet/rechargeInitiate a SATIM top-up. Returns a form URL.
POST/wallet/recharge/offlineSubmit a bank-transfer / cheque proof for admin approval.
POST/wallet/transferSend funds to a single recipient.
POST/wallet/transfer/bulkSend funds to many recipients. Partial success supported.

The URL prefix /wallet is a legacy path — the endpoints power the MyTPE balance described above. Do not rename it in your code; it's a stable public contract.

The balance object

FieldTypeDescription
idstring (uuid)Unique identifier.
currencystringISO code, always DZD today.
balancestringBalance as a decimal string, e.g. "1250.75".
balance_centimesintegerBalance in centimes (balance × 100), safe for arithmetic.
statusstringactive, frozen, or closed.
versionintegerOptimistic-locking counter, bumped on every ledger write.
created_atstring (ISO 8601)Account creation timestamp.

Get balance

Request
curl https://api.mytpe.appp/api/ext/wallet \
  -H "X-Api-Key: $MYTPE_API_KEY" \
  -H "X-Api-Secret: $MYTPE_API_SECRET"
200 OK
{
  "data": {
    "id": "9a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
    "currency": "DZD",
    "balance": "1250.75",
    "balance_centimes": 125075,
    "status": "active",
    "version": 42,
    "created_at": "2026-05-01T09:00:00.000000Z"
  }
}

Recharge (top-up)

Kicks off a SATIM transaction and returns a hosted form URL. Redirect the trader to form_url; on success their balance is credited automatically. The response's recharge.status starts as pending and moves to succeeded once the gateway confirms.

Body

FieldTypeRequiredNotes
amountintegeryesIn centimes (DA × 100). Must fall between recharge_min_amount and recharge_max_amount from the balance settings.

Headers

HeaderNotes
X-Idempotency-KeyStable 8–128 char string. A retry with the same key returns the original recharge and form_url.
Accept-LanguageOptional. fr (default), en, or ar — drives the SATIM form language.
Request
curl -X POST https://api.mytpe.appp/api/ext/wallet/recharge \
  -H "X-Api-Key: $MYTPE_API_KEY" \
  -H "X-Api-Secret: $MYTPE_API_SECRET" \
  -H "X-Idempotency-Key: 01JHV6MG9WK6QVBPT7RC2QYA0P" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 500000 }'
200 OK
{
  "data": {
    "recharge": {
      "id": "7c8d9e0f-1a2b-3c4d-5e6f-7a8b9c0d1e2f",
      "wallet_id": "9a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
      "amount": "5000.00",
      "amount_centimes": 500000,
      "status": "pending",
      "transaction_id": "b4c5d6e7-f8a9-0b1c-2d3e-4f5a6b7c8d9e",
      "created_at": "2026-06-15T10:22:00.000000Z"
    },
    "form_url": "https://cib.satim.dz/payment/merchants/mytpe/payment.html?...."
  }
}

Only traders can recharge

Non-trader accounts get 403 WALLET_RECHARGE_NOT_ALLOWED. In v1, payer users receive funds only via a trader-initiated transfer.

Offline recharge (bank transfer / cheque)

Use this when the trader has already paid you via a bank wire, cheque, or any out-of-band channel and you want the balance credited without going through SATIM. You upload a proof file (receipt scan or cheque photo); an admin reviews it and, on approval, credits the balance. Nothing hits the ledger until the admin approves — a rejected recharge never touches the balance.

Requestmultipart/form-data

FieldTypeRequiredNotes
amountintegeryesCentimes. Subject to the same recharge_min_amount / recharge_max_amount as the online flow.
prooffileyesjpg, jpeg, png, or pdf. Max 10 MB. Store one file per recharge.
referencestringno≤ 191 chars. Your bank wire id, cheque number, or any other correlation string — surfaced to the reviewing admin.

Headers

HeaderNotes
X-Idempotency-KeyStable 8–128 char string. Retrying the same key returns the original recharge without creating a duplicate submission.
Content-Typemultipart/form-data (curl sets this automatically when you use -F).
Request
curl -X POST https://api.mytpe.appp/api/ext/wallet/recharge/offline \
  -H "X-Api-Key: $MYTPE_API_KEY" \
  -H "X-Api-Secret: $MYTPE_API_SECRET" \
  -H "X-Idempotency-Key: bank-wire-2026-07-19-A" \
  -F "amount=500000" \
  -F "reference=BNP-WIRE-2026-07-19-A" \
  -F "proof=@/path/to/receipt.pdf"
201 Created
{
  "data": {
    "recharge": {
      "id": "8d9e0f1a-2b3c-4d5e-6f7a-8b9c0d1e2f3a",
      "wallet_id": "9a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
      "amount": "5000.00",
      "amount_centimes": 500000,
      "method": "offline",
      "status": "offline_pending",
      "transaction_id": null,
      "offline_reference": "BNP-WIRE-2026-07-19-A",
      "proof_url": "https://api.mytpe.appp/storage/proofs/receipt.pdf",
      "admin_notes": null,
      "reviewed_at": null,
      "created_at": "2026-07-19T14:22:00.000000Z"
    }
  }
}

Offline recharge lifecycle

The status field transitions through:

StatusMeaning
offline_pendingSubmitted, waiting for an admin to review the proof. Balance unchanged.
succeededAdmin approved. The balance has been credited with amount_centimes and reviewed_at is set.
offline_rejectedAdmin rejected. admin_notes carries the reason. Balance unchanged. Terminal — resubmit as a new recharge if needed.

Poll GET /wallet/recharges/{id} (or list via GET /wallet/recharges?status=offline_pending) to observe the transition, or subscribe to the wallet.recharged webhook — it fires on the approval, exactly like an online recharge.

Common errors

HTTPerrorWhen
403WALLET_RECHARGE_NOT_ALLOWEDCaller is not a trader.
422WALLET_RECHARGE_DISABLEDRecharges are globally disabled in settings.
422WALLET_AMOUNT_OUT_OF_RANGEamount outside the configured min/max.
422validationproof missing, wrong mime type, or over 10 MB.
422WALLET_RECHARGE_NOT_PENDING_OFFLINEYou (or an admin acting on your behalf) tried to approve/reject a recharge that already left offline_pending.

Never treat `offline_pending` as spendable

The offline_pending amount is not part of the balance. It only lands on the ledger when an admin approves the recharge. Race conditions where you kick off a transfer before approval will fail with WALLET_INSUFFICIENT_FUNDS.

Transfer to one user

Sends funds from the caller's balance to a single recipient. The recipient is resolved by exact match against email, phone, or id.

Body

FieldTypeRequiredNotes
recipientstringyesEmail, phone, or user id.
amountintegeryesCentimes. Subject to transfer_min_amount / transfer_max_amount and the rolling 24 h transfer_daily_limit.
notestringno≤ 280 chars. Attached to the ledger row.
Request
curl -X POST https://api.mytpe.appp/api/ext/wallet/transfer \
  -H "X-Api-Key: $MYTPE_API_KEY" \
  -H "X-Api-Secret: $MYTPE_API_SECRET" \
  -H "X-Idempotency-Key: transfer-invoice-4587" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": "amina@example.com",
    "amount": 150000,
    "note": "Refund invoice #4587"
  }'
200 OK
{
  "data": {
    "id": "6a5b4c3d-2e1f-0a9b-8c7d-6e5f4a3b2c1d",
    "from_wallet_id": "9a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
    "to_wallet_id": "1e2d3c4b-5a6f-7e8d-9c0b-1a2f3e4d5c6b",
    "amount": "1500.00",
    "amount_centimes": 150000,
    "status": "completed",
    "note": "Refund invoice #4587",
    "created_at": "2026-06-15T11:00:00.000000Z"
  }
}

Common errors

HTTPerrorWhen
403WALLET_TRANSFER_SENDER_NOT_TRADERThe caller is not a trader (should not happen for API-key traffic).
422WALLET_TRANSFER_RECIPIENT_IS_TRADERThe lookup resolved to a trader account (blocked in v1).
404WALLET_RECIPIENT_NOT_FOUNDNo user matches recipient.
422WALLET_INSUFFICIENT_FUNDSBalance can't cover the amount.
422WALLET_TRANSFER_DAILY_LIMITSum of transfers in the last 24 h exceeds the configured cap.
422WALLET_SELF_TRANSFERRecipient resolved to the caller.

Transfer to many users (bulk)

Send funds to up to 100 recipients in one request. Each item is processed independently — failures on one row do not roll back successful rows. The response reports per-item outcomes and a summary count.

Body

FieldTypeRequiredNotes
transfersarrayyes1–100 items.
transfers[].recipientstringyesEmail, phone, or user id.
transfers[].amountintegeryesCentimes.
transfers[].notestringno≤ 280 chars.
transfers[].referencestringno≤ 64 chars. Echoed back on the result row so you can correlate with your own DB (e.g. an invoice id).

Idempotency: the caller supplies one batch X-Idempotency-Key; per-item keys are derived server-side ({batchKey}:{index}). Re-sending the same batch is safe — already-processed rows are returned as their original success result, no double debit.

Request
curl -X POST https://api.mytpe.appp/api/ext/wallet/transfer/bulk \
  -H "X-Api-Key: $MYTPE_API_KEY" \
  -H "X-Api-Secret: $MYTPE_API_SECRET" \
  -H "X-Idempotency-Key: payroll-2026-06-15" \
  -H "Content-Type: application/json" \
  -d '{
    "transfers": [
      { "recipient": "amina@example.com",   "amount": 150000, "reference": "INV-4587" },
      { "recipient": "yacine@example.com",  "amount":  50000, "reference": "INV-4588" },
      { "recipient": "notfound@example.com","amount":  25000, "reference": "INV-4589" },
      { "recipient": "trader@another.com",  "amount":  10000, "reference": "INV-4590" }
    ]
  }'
200 OK
{
  "data": {
    "results": [
      {
        "index": 0,
        "recipient": "amina@example.com",
        "reference": "INV-4587",
        "status": "success",
        "transfer_id": "6a5b4c3d-2e1f-0a9b-8c7d-6e5f4a3b2c1d",
        "amount_centimes": 150000
      },
      {
        "index": 1,
        "recipient": "yacine@example.com",
        "reference": "INV-4588",
        "status": "success",
        "transfer_id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
        "amount_centimes": 50000
      },
      {
        "index": 2,
        "recipient": "notfound@example.com",
        "reference": "INV-4589",
        "status": "failed",
        "error_code": "WALLET_RECIPIENT_NOT_FOUND",
        "message": "No MyTPE user matches this identifier."
      },
      {
        "index": 3,
        "recipient": "trader@another.com",
        "reference": "INV-4590",
        "status": "failed",
        "error_code": "WALLET_TRANSFER_RECIPIENT_IS_TRADER",
        "message": "Trader-to-trader transfers are disabled."
      }
    ],
    "summary": {
      "total": 4,
      "succeeded": 2,
      "failed": 2
    }
  }
}

Response status is always 200 on partial success

The bulk endpoint returns 200 OK even if some rows fail — inspect results[].status and summary to decide what to retry. A 400/422 is only returned when the whole request is malformed (bad JSON, missing transfers, more than 100 items, etc.).

Bulk-only error codes

Rows can carry any of the common transfer errors above, plus:

error_codeWhen
WALLET_TRANSFER_UNEXPECTED_ERRORAn unhandled server error. Retry with the same batch idempotency key.

Best practices

  • Always send X-Idempotency-Key. Every write endpoint requires it; without one you get 422 WALLET_IDEMPOTENCY_KEY_REQUIRED. Choose a stable value derived from your own business record (invoice id, payroll run id, etc.) so retries collapse cleanly.
  • Trust balance_centimes, not balance. The decimal string is for display only — never do arithmetic against it in your code.
  • For bulk, prefer larger batches over many singles. One 100-item batch is far cheaper than 100 individual calls and gets you a single consolidated report.
  • Handle WALLET_TRANSFER_DAILY_LIMIT gracefully. If you hit it mid-batch, remaining rows will keep failing until the 24 h rolling window frees up.

On this page