A self-hosted captcha solving reseller platform built with FastAPI. Acts as a proxy between your users and the Solverify upstream API, allowing you to resell captcha solving services with your own branding, user management, and billing.
- First-run setup wizard — configure site name, API keys, costs, and admin account through a guided UI
- User authentication — register/login with email & password, API key generation
- Captcha task proxying — forwards
createTask/getTaskResultto upstream Solverify API - Balance management — per-user balance with atomic deductions on task completion
- Thread limiting — per-user concurrent task limits (configurable by admins, default set during setup)
- Role system — User → Admin → Superadmin hierarchy
- Superadmin (created during setup) cannot be banned or demoted
- Admins can manage users, balances, and task types
- Only superadmins can promote/demote admins
- Admin dashboard — user management with search, pagination, ban/unban, balance updates, thread limit editing, role management
- Admin settings panel — superadmins can change Solverify key, solve cost, site name, description, and default thread limit from the UI
- Task type management — admins add/remove/enable/disable task types from the admin panel; dashboard dropdown loads dynamically
- User profile page — change email and password at
/profile - Ban system — banned users are rejected at the API key level
- Dark themed UI — clean, modern design with Jinja2 templates served by FastAPI
├── app/
│ ├── main.py # FastAPI app, lifespan, middleware, exception handlers
│ ├── config.py # Pydantic settings (env vars)
│ ├── database.py # SQLAlchemy async engine & session
│ ├── models.py # User, Task, SiteSettings, TaskType models
│ ├── schemas.py # Pydantic request/response schemas
│ ├── auth.py # Password hashing (bcrypt), API key generation
│ ├── dependencies.py # get_current_user_by_client_key, InvalidClientKeyError
│ ├── site_settings.py # Helper to check setup status
│ ├── routers/
│ │ ├── auth_router.py # POST /register, POST /login
│ │ ├── captcha_router.py # POST /createTask, /getTaskResult, /getBalance, /me, /updateProfile
│ │ ├── admin_router.py # /admin/* endpoints (users, balance, ban, roles, task types, threads)
│ │ ├── pages_router.py # GET /, /dashboard, /admin, /profile (HTML pages)
│ │ └── setup_router.py # GET/POST /setup/
│ ├── services/
│ │ ├── balance_service.py # Atomic add/deduct/set balance operations
│ │ ├── solverify_client.py # httpx client for upstream Solverify API
│ │ └── thread_limiter.py # In-memory per-user concurrency limiter
│ ├── templates/
│ │ ├── base.html # Base template with global styles
│ │ ├── auth.html # Login/Register page
│ │ ├── setup.html # First-run setup wizard
│ │ ├── dashboard.html # User dashboard
│ │ ├── admin.html # Admin panel
│ │ └── profile.html # User profile/settings
│ └── tests/ # Unit tests + property-based tests (Hypothesis)
├── .env # Environment variables (not committed)
├── .env.example # Example env file
├── requirements.txt # Python dependencies
└── app.db # SQLite database (auto-created)
git clone <repo-url>
cd Solverify-Reseller
python -m venv venv
source venv/bin/activate
pip install -r requirements.txtcp .env.example .envEdit .env with your settings:
DATABASE_URL=sqlite+aiosqlite:///./app.db
SOLVERIFY_BASE_URL=https://solver.solverify.netThe Solverify client key, solve cost, and other settings are configured through the setup wizard and admin settings panel — not in
.env.
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000On first run, visit http://localhost:8000 — you'll be redirected to the setup wizard where you configure:
- Site info — name and description
- API config — Solverify client key, solve cost per captcha, default thread limit
- Admin account — email and password (becomes superadmin)
All captcha API endpoints return HTTP 200 with errorId: 0 on success or errorId: 1 with error details on failure.
{
"email": "user@example.com",
"password": "minimum8chars"
}Response: {"message": "User registered successfully"}
{
"email": "user@example.com",
"password": "minimum8chars"
}Response: {"apiKey": "hex-api-key-64-chars"}
{
"clientKey": "your-api-key"
}Response:
{
"errorId": 0,
"balance": 100.0
}{
"clientKey": "your-api-key",
"task": {
"type": "TurnstileTaskProxyless",
"websiteURL": "https://example.com",
"websiteKey": "0x4AAAAAAA...",
"action": "managed",
"cdata": "optional-custom-data"
}
}Response:
{
"errorId": 0,
"taskId": "uuid-task-id"
}Possible errors:
ERROR_INSUFFICIENT_BALANCE— not enough creditsERROR_TOO_MANY_REQUESTS— thread limit reachedERROR_UPSTREAM— upstream Solverify API error
{
"clientKey": "your-api-key",
"taskId": "uuid-task-id"
}Response:
{
"errorId": 0,
"status": "completed",
"solution": {
"value": "captcha-token-here"
}
}Balance is deducted on first completed status.
{
"clientKey": "your-api-key"
}Response:
{
"email": "user@example.com",
"balance": 100.0,
"is_admin": false,
"is_superadmin": false,
"banned": false,
"thread_limit": 20
}Returns enabled task types (no auth required).
{
"types": [
{"id": 1, "name": "Turnstile"}
]
}{
"clientKey": "your-api-key",
"email": "new@email.com",
"currentPassword": "current-password",
"newPassword": "new-password-optional"
}All admin endpoints require an admin API key. These are not listed in /docs.
| Endpoint | Method | Description |
|---|---|---|
/admin/users |
GET | List users (paginated, searchable). Pass X-API-Key header. Query params: page, per_page, search |
/admin/updateBalance |
POST | Update user balance. Body: {clientKey, userId, value}. Value format: +100 (add), -100 (remove), 100 (set exact) |
/admin/addBalance |
POST | Add balance by email. Body: {clientKey, targetEmail, amount} |
/admin/ban |
POST | Ban user. Body: {clientKey, userId}. Cannot ban self or superadmins |
/admin/unban |
POST | Unban user. Body: {clientKey, userId} |
/admin/toggleAdmin |
POST | Promote/demote admin (superadmin only). Body: {clientKey, userId} |
/admin/setThreadLimit |
POST | Set user thread limit. Body: {clientKey, userId, threadLimit}. 0 = unlimited |
/admin/taskTypes |
GET | List all task types (admin). Pass X-API-Key header |
/admin/taskTypes |
POST | Add task type. Body: {clientKey, name} |
/admin/taskTypes/toggle |
POST | Enable/disable task type. Body: {clientKey, id} |
/admin/taskTypes/delete |
POST | Delete task type. Body: {clientKey, id} |
/admin/settings |
GET | Get site settings (superadmin only). Pass X-API-Key header |
/admin/settings |
POST | Update site settings (superadmin only). Body: {clientKey, site_name, site_description, solverify_client_key, solve_cost, default_thread_limit} |
| Column | Type | Description |
|---|---|---|
| id | Integer | Primary key |
| String(255) | Unique, indexed | |
| password_hash | String(255) | bcrypt hash |
| api_key | String(64) | Unique, nullable, indexed |
| balance | Float | Default 0.0 |
| is_admin | Boolean | Default false |
| is_superadmin | Boolean | Default false |
| banned | Boolean | Default false |
| thread_limit | Integer | Default from site settings |
| created_at | DateTime | UTC timestamp |
| Column | Type | Description |
|---|---|---|
| id | Integer | Primary key |
| task_id | String(36) | Upstream task UUID, unique |
| user_id | Integer | FK → users.id |
| billed | Boolean | Whether balance was deducted |
| created_at | DateTime | UTC timestamp |
| Column | Type | Description |
|---|---|---|
| id | Integer | Always 1 (singleton) |
| site_name | String(255) | Display name |
| site_description | String(500) | Site description |
| solverify_client_key | String(255) | Upstream API key |
| solve_cost | Float | Cost per solved captcha |
| default_thread_limit | Integer | Thread limit for new users |
| setup_complete | Boolean | Whether setup wizard was completed |
| Column | Type | Description |
|---|---|---|
| id | Integer | Primary key |
| name | String(100) | Unique task type name |
| enabled | Boolean | Whether shown in dashboard dropdown |
Each user has a thread_limit that controls how many createTask requests can be in-flight concurrently. The limiter is in-memory (resets on server restart).
- Default limit is set during setup wizard
- Admins can change per-user limits from the admin panel
- Set to
0for unlimited - When limit is reached, API returns
ERROR_TOO_MANY_REQUESTS
| Role | Can manage users | Can change roles | Can be banned |
|---|---|---|---|
| User | No | No | Yes |
| Admin | Yes | No | Yes (by other admins) |
| Superadmin | Yes | Yes | No |
The superadmin account is created during the setup wizard and cannot be demoted or banned.
pytest app/tests/ -vThe test suite includes unit tests and property-based tests using Hypothesis.
- Backend: FastAPI + SQLAlchemy (async) + SQLite (aiosqlite)
- Auth: passlib + bcrypt for password hashing
- HTTP Client: httpx for upstream API calls
- Frontend: Jinja2 templates, vanilla JS, no build step
- Testing: pytest + Hypothesis (property-based testing)