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
enabledbooleanWhether the balance feature is switched on for this account. When false, writes return WALLET_NOT_ENABLED.
idstring (uuid)Unique identifier.
currencystringISO code, always DZD today.
balancestringBalance as a decimal string, for example, "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.app/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"
  }
}

Response

Status200 OK
BodyOne balance object, wrapped in data.

Errors follow the shared envelope. See Responses.

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.app/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.

Response

Status200 OK
BodyThe top-up and a form_url to redirect the payer to, wrapped in data. The balance does not change until the gateway confirms.

Errors follow the shared envelope. See Responses.

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.app/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.app/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.

Response

Status201 Created
BodyThe pending top-up, wrapped in data. It stays pending until an admin approves the proof.

Errors follow the shared envelope. See Responses.

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.app/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.

Response

Status201 Created
BodyThe transfer, wrapped in data. A replay of the same X-Idempotency-Key returns 200 OK with the original transfer — nothing is created and no money moves twice.

Errors follow the shared envelope. See Responses.

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 (for example, 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.app/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, or more than 100 items).

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.

Response

Status200 OK
BodyA report in data with one row per item. 200 even when some rows failed — read results[].status to see which landed.

Errors follow the shared envelope. See Responses.

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 (an invoice id, a payroll run id) 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