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)