---
title: "Python credits: one export, one charge"
description: "Add an online credit charge and durable retry key before doing a real CSV job."
slug: "/docs/v1/tutorials/python/credits"
section: "Practical tutorials"
order: 1004
audience: "all"
level: "beginner"
last_updated: "2026-10-26"
version: "v1"
related: ["/docs/v1/tutorials/python", "/docs/v1/sdks/python", "/docs/v1/sdks/capabilities"]
draft: false
layout: "guide"
---

## The simple rule

**Confirm one credit charge, then do one job.** Checking a feature does not spend a credit. A credit is a usage unit, not a euro payment or subscription invoice.

This example writes a real CSV and records its job in a private SQLite file. It uses the real SDK and a real metered Sandbox license. It does not mock billing, collect bank cards or sell credit packs.

## 1. Prepare a credit license

1. Your KDPika platform plan must include usage credits. Check [Billing & usage](/billing) and your current plan capabilities.
2. Create a separate tutorial software plan with **Uses credits** enabled. Start without floating seats to learn one model at a time.
3. Issue its Sandbox license to an email you control.
4. Open that license → **Credits** and grant a small whole-number balance, such as 3 units. The application consumes units; it does not carry a developer management key for granting them.

The operation itself is credit-gated in this example; it does not additionally require `csv_export`. If your real product combines feature flags and credits, design and test both the first authorization and recovery of already-paid jobs. Do not add a feature check after the final successful charge and accidentally reject the paid operation because the balance reached zero.

Dashboard example: Define access and entitlements

Open: Plans & features → New plan

Example data only; use your own real dashboard.

Plan name: Export credits

Max devices: 2

Apps running at once: Leave blank — Learn credit charging without mixing in shared seats.

Usage credits (pay per use): 

Feature keys: Leave blank — The credit job is authorized by its signed consume receipt.

Buttons: Create plan

New metered licenses start with zero credits. Issue a new Sandbox license for this plan, then add units on that license.

Dashboard example: Manage usage credits

Open: Licenses → Manage → Usage credits

Example data only; use your own real dashboard.

Action: Add credits

Credits: 3

Note (optional): Initial tutorial allowance

Available credits: 0 → 3

Buttons: Save credit transaction

Usage units, not money. This does not charge a card or refund a payment.

## 2. Run one job

Use the extracted [Python projects](/docs/downloads/kdpika-python-projects-1.0.0.zip), the same SDK installation and App ID/Sandbox environment setup from the [desktop](/docs/v1/tutorials/python/desktop) or [web](/docs/v1/tutorials/python/web) guide.

```bash
python credit_export.py --job export-order-001 --text "My paid export"
```

On first activation, enter the credit license key privately and its matching email. After a verified successful consume, the app prints the saved CSV path. Open **Credits** in KDPika: one unit was consumed.

## 3. Repeat the identical command

Run that exact command again, with the same job name, message, license and private state folder. The completed job returns its saved output without consuming another unit.

A genuinely new export uses a new logical job name. A retry of an uncertain export uses its **original** name. Do not generate a fresh UUID every time the retry button is clicked.

| Situation | Safe response |
| --- | --- |
| Signed charge succeeds | Do that one job, even if the resulting balance is zero |
| Not enough credits | Do not do the job. Ask the publisher to add units, then retry the same job |
| Connection drops before a reply | The outcome is uncertain; retain the pending job and retry its original key |
| Response is replayed | It identifies the original charge; do not perform a second independent job |
| Job name reused with different data/license | Refuse and ask the user to resume the original job or create a genuinely new one |

## 4. Understand the code

`client.consume(1, idempotency_key=...)` verifies the signed charge response. The server prevents a repeated request key from charging twice. SQLite retains the job **before** that request, and a deterministic output path/atomic replacement makes this CSV work repeatable after a crash.

The job journal is local application data, not proof of a valid license. Every pending run checks the online license and requires a verified consume receipt. A zero balance is allowed only for recovery of a receipt that already paid for that same operation; it is not permission for another job.

{{sample:python-projects/credit_export.py}}

## 5. Use it in a web application

Import `run_job` into a backend worker. First verify your own user session, authorization and CSRF; associate the job with that authorized user/tenant in your real job table. Then call `run_job(client, stable_job_id, text, private_folder)` with server-controlled values. Never treat a user-supplied arbitrary job ID/license as permission to read another user's saved output.

The downloadable credit script is a single-customer local example. It does not add a public credit-consume route or a multi-tenant job system. Adapt it to the existing server-side account/job model, not browser code. SQLite serializes this tutorial's local jobs; a distributed backend needs its own transactional job ownership and idempotent downstream effects.

## 6. Read the balance correctly

```python
from kdpika_licensing import CreditOperationError

try:
    balance = client.get_balance()
    show_balance(balance)
except CreditOperationError:
    show_message("We could not refresh your balance. Please reconnect.")
# show_balance/show_message are callbacks in your own UI.
```

`get_balance()` makes an online signed check; a replayed receipt's balance belongs to its original transaction. Do not use an old receipt to display today's balance. Credit actions do not have an offline fallback.

## 7. Know the boundary

The server deduplicates the charge; your application deduplicates its work. This CSV example uses a stable file and journal. If your job sends email, transfers money or calls another service, use that service's own idempotency/recovery mechanism too. A SQLite row cannot guarantee exactly-once external effects.

Keep the journal and license state private and persistent. Never clear them just to retry an uncertain charge. A customer portal can show balances and recent transactions, but has no credit-pack checkout, invoices or payment refund tools.

[Python feature map](/docs/v1/tutorials/python) · [Credits API reference](/docs/v1/licensing/credits) · [Customer portal](/docs/v1/workspace/customer-portal).
