Documentation
API reference and the automation SDK guide for building on Xgodo.
- 18
- v1 routers
- 231
- endpoints
- 37
- pages
Start here
Client API (v2)
The stable public client API. For the complete internal surface, see the v1 reference.
Authentication
Bearer Token Authentication
All API endpoints require authentication using a Bearer token. Include the following header in your requests:
Authorization: Bearer <your_access_token>Jobs
Manage job postings, retrieve applicants, and handle job-related operations.
/api/v2/jobs/applicantsRetrieve job applicants
Retrieves a list of job applicants for a specified job. Allows for sorting, pagination, and searching within job applicants. Note: per-job task count aggregates (`total_task`, `job_done`, `running_tasks`, `pending_tasks`, `satisfied_tasks`, `declined_tasks`, `failed_tasks`) are intentionally NOT included in the v2 response — they were rarely consumed and computing them under concurrent load is the cluster's hottest read. If you need them, derive client-side by paging with `status` filters.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | The ID of the job for which to retrieve applicants |
task_id | string | No | The ID of the job task or planned task. Accepts either a job_task_id or planned_task_id - if a planned_task_id is provided, it will be resolved to its associated job_task_id. Used when only one task details is needed. If present, the task result list array will contain only one task or empty array if task not found. |
sortby | string | No | Field by which to sort job applicants |
order | string | No | Sort order. Can be 'asc' for ascending or 'desc' for descending |
page | integer | No | Page number for pagination |
limit | integer | No | Number of applicants per page |
hours_ago | integer | No | The day interval, specified as the number of days starting from the given hours_ago up to today |
status | string | No | The status of the task ('processing', 'confirmed', 'notcomplete', 'declined', 'pending') |
search | string | No | Search term to filter applicants based on worker name, job proof, job title, comment, or worker IP |
Responses
| Status | Description |
|---|---|
| 200 | Successfully retrieved job applicantsReturns job details with applicant information including planned_task, job tasks (each carrying its own `job_price` in USD — the actual amount paid for that task; defaults to the parent job's `job_price` at task creation and may diverge thereafter), total tasks, job done count, pending tasks, satisfied tasks, declined tasks, and failed tasks |
| 400 | Invalid request data or planned task exists but has no associated job task{"error": "Invalid task_id format"} or {"error": "Planned task {id} exists but has no associated job task (job_task_id is null)"} |
| 404 | Job not found, task_id is neither a valid job task ID nor a planned task ID{"error": "Job with this Id not found"} or {"error": "task_id {id} is neither a valid job task ID nor a planned task ID"} |
/api/v2/jobs/applicantsUpdate job applicants' task statuses
Updates the status of specified job tasks. It supports changing the status, adding comments, and handles referral bonuses if the task is confirmed.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
JobTasks_Ids | array | Yes | An array of job task IDs or planned task IDs to be updated. Accepts a mix of job_task_ids and planned_task_ids - planned task IDs will be resolved to their associated job_task_ids. |
status | string | Yes | The new status to set for the job tasks |
job_id | string | Yes | The ID of the job to which the tasks belong |
comment | string | No | Optional comment to add regarding the task update |
Responses
| Status | Description |
|---|---|
| 200 | Successfully updated job task statuses.Returns the updated job task details |
| 400 | Bad request. Invalid input data, invalid status provided, or planned task exists but has no associated job task.{"error": "Invalid status..."} or {"error": "Planned task {id} exists but has no associated job task (job_task_id is null)"} |
| 404 | Job or job tasks not found, or ID is neither a valid job task ID nor a planned task ID.{"error": "Job Task with this Id not found"} or {"error": "ID {id} is neither a valid job task ID nor a planned task ID"} |
/api/v2/jobs/detailsGet job details by ID
Returns job details for a specific job ID. When the job is linked to an automation (`automation_id` is set), the response also includes the automation's `automation_parameters_schema`, `job_variables_schema`, and `bucket_schema` so callers can validate / build UIs around the `automationParameters`, `jobVariables`, and bucket payloads accepted by the automation. Each schema follows the `IInputSchema` shape (`{ fields: IFieldSchema[] }`) and is `null` when the automation has no schema configured or the job has no linked automation. Note: per-job task count fields (`totla_tasks_count`, `tasks_done_count`, `tasks_confirmed_count`, `tasks_running_count`, `tasks_pending_count`) are intentionally NOT included in the v2 response — they were rarely consumed and computing them under concurrent load is the cluster's hottest read.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string | No | Unique identifier for the job |
{ "_id": "68910c4a5e1c7092a1fdc03a", "job_id": "68910c4a5e1c7092a1fdc03a", "job_type": "devices_automation", "payment_type": "action", "title": "Create a Google account", "category": ["Google"], "description": "Create a Google account. ...", "proof": "Google account email. ...", "Target_Workers": ["worldwide"], "few_times": true, "premium": false, "featured": false, "duration": "3", "positions": "3", "job_price": 0.06, "price": "0.30", "status": "active", "files": [...], "added": "2025-08-04T19:38:50.204Z", "user": {...}, "automation_id": "83ba7359-d8eb-4374-8ecb-055af01fddfe", "automation_parameters_schema": { "fields": [ { "name": "maxRetries", "type": "number", "required": false, "min": 1, "max": 10 } ] }, "job_variables_schema": { "fields": [ { "name": "email", "type": "string", "required": true }, { "name": "password", "type": "string", "required": true } ] }, "bucket_schema": { "fields": [ { "name": "sessionToken", "type": "string", "required": false } ] }}/api/v2/jobs/check-uniquenesCheck for uniqueness of job proofs
This endpoint checks if a given search term exists in the job proofs. You can specify the search option to look for exact words or substrings.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
search | string | Yes | The term to search for in job proofs |
option | string | No | The search option to use. Can be "word" for exact word matching or "charactor" for substring matching |
Responses
| Status | Description |
|---|---|
| 200 | Successfully checked for uniquenessReturns a boolean indicating if the search term is unique |
| 400 | Bad request. Either the search term is missing, or an invalid search option is providedError message describing the validation error |
/api/v2/jobs/myjobsRetrieve a list of jobs that you have posted
Returns a paginated list of jobs the caller has posted under `data` (with `count`, paginated by `page`), plus a separate list of jobs shared with the caller under `shared_data` (with `shared_count`, paginated independently by `shared_page` — the current shared page is echoed back as `shared_page`). Each entry in `shared_data` carries `permission: "view" | "edit"` and `owner_username`. The response is a thin projection of the Job documents — `job_price` and `price` are stringified. Results are sorted by `added` descending. Note: per-job task counters (`job_done`, `running_task`, `pending_task`) and planned-task counters (`used_planned_tasks`, `added_planned_tasks`) are not included. To get per-task data for a specific job, use the `/api/v2/jobs/applicants` endpoint.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number for the jobs you posted (the `data` list) |
limit | integer | No | Number of items per page (applies to both lists; default 10, max 200) |
shared_page | integer | No | Page number for the jobs shared with you (the `shared_data` list). Paginates independently of `page`. Defaults to 1. |
job_id | string | No | Filter jobs by a specific job ID |
status | string | No | Filter jobs by status (e.g., 'active', 'completed') |
orientation | string | No | The orientation for the data sorting (e.g., 'asc' or 'desc') |
Query | string | No | Search query for job title or description |
SortBY | string | No | Field to sort the results by |
/api/v2/jobs/searchSearch and filter jobs
Returns a paginated list of jobs matching the provided filters. Supports search, category, price, location, and other filters.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
page | string | No | Page number for pagination |
limit | string | No | Number of jobs per page |
searchTerm | string | No | Search term for job title or description |
job_category | string | No | Filter by job category |
min_price | string | No | Minimum job price |
location | string | No | Filter by worker location |
Featured | boolean | No | Filter for featured jobs |
Premium | boolean | No | Filter for premium jobs |
orientation | string | No | Sort orientation (asc, desc) |
SortBY | string | No | Field to sort by |
/api/v2/jobs/submitCreate a new job posting
This endpoint allows users to create a new job posting. It requires specific details about the job, including title, description, category, number of positions, job price, and duration.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
title | string | Yes | The title of the job posting |
category | string | Yes | Category of the job |
description | string | Yes | Detailed description of the job |
proof | string | Yes | Proof or evidence related to the job posting |
is_proof_file | boolean | No | Indicates if the proof is a file or text |
auto_rate | boolean | No | Whether the job should be automatically rated |
vcode | string | No | Verification code for the job posting |
Target_Workers | array | No | List of target workers or countries |
few_times | boolean | No | Indicates if the job can be posted a few times |
premium | boolean | No | Indicates if the job is premium |
featured | boolean | No | Whether the job is featured or not |
duration | integer | Yes | Duration for which the job is posted (in days) |
positions | integer | Yes | Number of available positions for the job |
daily_limit | integer | No | Daily limit for the job posting |
job_price | number | Yes | Price for posting the job |
Responses
| Status | Description |
|---|---|
| 201 | Job created successfullySuccess message indicating job creation |
| 400 | Bad request. Invalid or missing required fieldsList of validation error messages |
/api/v2/jobs/update-statusUpdate job status
Updates the status of a specific job.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | The ID of the job |
status | string | Yes | New status for the job (active, inactive) |
Responses
| Status | Description |
|---|---|
| 204 | Job status updated successfully |
| 400 | Invalid request body or status |
| 401 | Unauthorized action |
| 404 | Job not found or invalid job_id |
Tasks
Manage job tasks, apply for tasks, and submit task completions.
/api/v2/tasks/applyApply for task submission to a job
This API must be called before tasks/submit API for jobs that have job variables. This endpoint allows workers to apply to an active job. Job variables (if any) will be returned.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | Unique identifier for the job |
{ "_id": "68910c4a5e1c7092a1fdc03c", "var1": "Custom variable value 1", "var2": "Custom variable value 2"}/api/v2/tasks/detailsGet recent tasks or details of a specific job task
Returns the list of recent tasks of the user. If task_id query param is provided, it will return details of a single task.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
task_id | string | No | The ID of the job task to fetch details for |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number for pagination (starts from 1) |
limit | integer | No | Number of tasks to return per page |
{ "_id": "686f668db9c27eea026e60c7", "job_task_id": "686f668db9c27eea026e60c7", "worker_id": "676d3525e618d4ad99dae7ff", "job_id": "686f44cf8abf68ace53ebc8f", "job_title": "Create a Google account", "job_price": 0.06, "job_proof": "", "comment": "", "country_code": "IN", "worker_ip": "192.168.249.223", "status": "confirmed", "failureReason": null, "reviewed": null, "device_owner_share": null, "added": "2025-07-10T07:06:53.631Z", "updated": "2025-07-10T07:06:53.631Z", "proof_files_info": []}/api/v2/tasks/submitSubmit a task to a job
Submits a task to an active job. The endpoint operates in one of two modes selected by the target job: 1. Manual mode (default) — for regular jobs. Honors job-level gates (worker rating, country, allowed_workers, premium, few_times, custom_vars, predeclared file fields, positions, task_limit, Worker_Invites) and triggers task/job completion emails. 2. Automation data-dump mode — selected automatically when the target job is of type devices_automation, has automation_id set, and has no planned tasks. Used by an automation agent running another job to write structured data into a separate sink job. In this mode the endpoint: • requires the caller's token to carry job_task_id and remote_device_id (i.e. a running agent token); • requires job_proof to be valid JSON; • accepts files in the {name, extension, base64Data} shape, embedded into the JSON proof under proof[name]; • accepts an explicit status of "pending" (default), "failed", or "declined"; • derives device_id and the device-owner share from the agent's remote_device_id; • authorizes by requiring the running agent's parent job and the target data-dump job to share the same automation_id, AND the running job's owner to be the target job's owner or to appear in the target's shared_view / shared_edit; • skips manual-mode gates (rating/country/few_times/custom_vars/proof_files/positions/task_limit/Worker_Invites) and skips completion emails; • does not update automation iteration / success-rate / blacklist analytics — those are reserved for "real" agent-driven tasks created by the automation engine.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | ID of the job. The endpoint runs in one of two modes depending on the target job: "manual" mode for regular jobs, or "automation data-dump" mode when the target job is of type devices_automation with automation_id set and no planned tasks. |
job_proof | string | Yes | Proof of the task done. In automation data-dump mode this string MUST be valid JSON. |
custom_vars | object | No | Manual mode only. Custom vars obtained from tasks/apply. Required for jobs with variables. |
proof_files_base64 | array | No | Manual mode only. Array of required and optional proof files in base64 format, mapped to the job's predeclared file fields. |
status | string | No | Automation data-dump mode only. One of "pending", "failed", or "declined". Defaults to "pending". |
files | array | No | Automation data-dump mode only. Files to attach to the submission. Each file is written to disk and its public URL is embedded into the JSON job_proof under the key matching `name`. Each base64Data is capped at 5 MB. |
Responses
| Status | Description |
|---|---|
| 201 | Job task submitted successfullySuccess message indicating task submission, job task id and job id |
| 400 | Bad request — e.g. job_proof is not a string, or in automation data-dump mode job_proof is not valid JSON / files validation failed.Error message describing the validation failure |
| 403 | Forbidden. Returned for automation data-dump submissions when the caller is not a running agent (token must carry remote_device_id and job_task_id), the agent has no currently-running task, the running job and target job do not share the same automation_id, or the running job's owner has no access (owner / shared_view / shared_edit) to the target data-dump job.Error message describing the authorization failure |
| 404 | Job not found, running task's job not found, or device not foundError message indicating the missing resource |
/api/v2/planned_tasksList unassigned planned tasks for a job
Returns planned tasks for a job that have not yet been assigned to a job task (i.e. job_task_id is null). Supports search, pagination, and sort order. Only the job owner (or a user the job is shared with) can list its planned tasks.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | The ID of the job whose planned tasks should be listed |
search | string | No | Case-insensitive substring match against the planned task input (max 10,000 chars) |
page | integer | No | Page number for pagination (starts from 1). Defaults to 1 |
limit | integer | No | Number of planned tasks to return per page (1-100). Defaults to 10 |
sortOrder | string | No | Sort order by creation time. One of "asc" or "desc". Defaults to "desc" |
Responses
| Status | Description |
|---|---|
| 200 | Planned tasks (unassigned) fetched successfully. Only tasks whose job_task_id is null are returned.{"success": true, "data": {"job": {...}, "plannedTasks": [{"planned_task_id": "...", "job_id": "...", "input": "...", "added": "..."}], "total": 42}} |
| 400 | Bad request - Invalid query parameters{"success": false, "error": "Invalid job id"} |
| 404 | Job not found or user doesn't own the job{"success": false, "error": "Job not found"} |
| 500 | Internal server error{"success": false, "message": "Internal Server Error"} |
/api/v2/planned_tasks/submitSubmit planned tasks for a job
This endpoint allows users to submit multiple planned tasks for a specific job. Planned tasks are pre-defined inputs that will be used when workers apply for the job.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | The ID of the job to submit planned tasks for |
inputs | array | Yes | Array of input strings for planned tasks (usually JSON strings, minimum 1, maximum 10,000 characters per string) |
remote_device_id | string (UUID v4) | No | Optional device ID to pin all submitted tasks to a specific device. When set, the system will only assign these tasks to the specified device. If the device is not available, the task will be retried later. |
device_name | string | No | Optional device name (system-generated, two-word labels like "Atomic Mammal" or "Enormous Elephant") to pin all submitted tasks to a specific device. Resolved to a remote_device_id server-side. Ignored when remote_device_id is also provided. If the name matches more than one device, the request fails with 400 — use remote_device_id to disambiguate. |
run_immediately | boolean | No | If true, the system will attempt to assign the planned tasks immediately after creation (up to 3 attempts, 5 seconds apart). The response will include assignment_results indicating whether each task was assigned. Defaults to false. |
Responses
| Status | Description |
|---|---|
| 200 | Planned tasks submitted successfully. When run_immediately is true, includes assignment_results.{"success": true, "inserted_ids": [{"planned_task_id": "60c72b2f9b1e8c001c8e4d8a", "input": "{\"fname\":\"John\",\"email\":\"john@example.com\"}"}], "assignment_results": [{"planned_task_id": "60c72b2f9b1e8c001c8e4d8a", "assigned": true}]} |
| 400 | Bad request - Invalid input data, exceeds available positions, or validation errors{"success": false, "error": "Invalid job id"} or {"success": false, "message": "No enough positions left. Received X tasks in excess."} |
| 404 | Job not found or user doesn't own the job{"success": false, "error": "Job not found"} |
| 500 | Internal server error{"success": false, "message": "Internal Server Error"} |
/api/v2/tasks/deleteDelete a job task by ID
This endpoint allows employers to delete a job task. Accepts either a job_task_id or planned_task_id (a planned_task_id is resolved to its associated job task). Only tasks with certain statuses can be deleted to maintain data integrity (failed, declined, confirmed). The associated planned task is deleted along with the job task by default; pass keep_planned_task: true to instead preserve it (its job_task_id is reset to null) so it can be reused.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
task_id | string | Yes | The ID of the job task or planned task to delete. Accepts either a job_task_id or planned_task_id - if a planned_task_id is provided, it is resolved to its associated job task, which is then deleted. |
keep_planned_task | boolean | No | If true, the planned task associated with the deleted job task is preserved (its job_task_id is reset to null) so it can be reused; if false (default), that planned task is deleted along with the job task. Applies whether task_id is a job_task_id or a planned_task_id. |
Responses
| Status | Description |
|---|---|
| 200 | Job task deleted successfully. The associated planned task is deleted by default, or preserved with job_task_id set to null when keep_planned_task is true.{"success": true, "message": "Job task deleted successfully"} |
| 400 | Bad request - Invalid task ID, task cannot be deleted due to status restrictions, or planned task exists but has no associated job task{"success": false, "error": "Invalid task id"} or {"success": false, "message": "Cannot delete job task with status 'confirmed'. Only tasks with status: failed, declined can be deleted."} or {"success": false, "error": "Planned task {id} exists but has no associated job task (job_task_id is null)"} |
| 403 | Forbidden - User doesn't have permission to delete this job task{"success": false, "message": "You don't have permission to delete this job task"} |
| 404 | Job task not found, or task_id is neither a valid job task ID nor a planned task ID{"success": false, "message": "Job task not found"} or {"success": false, "error": "task_id {id} is neither a valid job task ID nor a planned task ID"} |
| 500 | Internal server error{"success": false, "message": "Internal Server Error"} |
/api/v2/planned_tasksDelete one or more planned tasks
Deletes unassigned planned tasks by their IDs. Planned tasks already assigned to a pending or successful job task cannot be deleted — reinitiate or delete the job task first. All IDs must belong to jobs owned by (or shared with edit permission to) the caller.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
planned_task_ids | string[] | Yes | Array of planned task IDs to delete (minimum 1). A planned task that is already assigned to a pending or successful job task cannot be deleted. |
Responses
| Status | Description |
|---|---|
| 200 | Planned tasks deleted successfully{"success": true} |
| 400 | Bad request - Invalid planned task id, or one of the planned tasks is already assigned to a pending/successful job task{"success": false, "message": "Cannot delete a planned task that is assigned to a pending / successful job task"} |
| 404 | One or more planned tasks not found, or the job is not owned by the caller{"success": false, "message": "Planned task not found"} |
| 500 | Internal server error{"success": false, "message": "Internal Server Error"} |
/api/v2/planned_tasks/reinitiateRe-initiate failed/declined tasks
This endpoint allows employers to re-initiate one or more failed or declined tasks. Accepts either a single task_id or an array of task_ids. This makes the planned tasks available again for workers to apply.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
task_id | string | No | The ID of a single job task to re-initiate (for backward compatibility) |
task_ids | string[] | No | An array of job task IDs to re-initiate. Either task_id or task_ids must be provided. |
Responses
| Status | Description |
|---|---|
| 200 | Task re-initiated successfully{"success": true, "message": "Task successfully reinitiated"} |
| 400 | Bad request - Invalid task ID or task cannot be reinitiated due to status restrictions{"success": false, "error": "Invalid task id"} or {"success": false, "message": "Cannot reinitiate task with status 'confirmed'. Only failed or declined tasks can be reinitiated."} |
| 403 | Forbidden - User doesn't have permission to reinitiate this task{"success": false, "message": "You don't have permission to reinitiate this task"} |
| 404 | Job task not found or no planned task associated with this job task{"success": false, "message": "Job task not found"} |
| 500 | Internal server error{"success": false, "message": "Internal Server Error"} |
Payments
Handle payment operations including withdrawals.
/api/v2/payments/withdrawWithdraw user balance
Initiate a withdrawal to a wallet address.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Amount to withdraw |
wallet_address | string | Yes | Wallet address to receive the withdrawal |
Responses
| Status | Description |
|---|---|
| 200 | Withdrawal successful{"msg": "withdraw done"} |
| 400 | Insufficient balance, below minimum payout, or invalid requestError message with specific reason |
| 401 | Unauthorized or missing token |
| 500 | Server error |
/api/v2/payments/withdrawalsGet recent withdrawal details
Returns the list & details of recent withdrawals made by the user.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number for pagination (starts from 1) |
limit | integer | No | Number of withdrawals to return per page |
Responses
| Status | Description |
|---|---|
| 200 | Details of the withdrawals made by the user |
| 401 | Unauthorized or missing token |
| 404 | No transactions found |
| 500 | Server error |
{ "total_count": 3, "transactions": [ { "_id": "67d9f0b57131be1625d301c4", "reference_id": "26795145", "user_id": "676d3619e618d4ad99dae899", "recipient_id": null, "crypto_uuid": null, "payment_type": "Withdrawal", "payment_method": "cryptomus", "payment_details": "", "transaction_fee": "0", "currency": "USD", "amount": "-10", "wallet_address": "2w", "status": "paid", "reason": null, "created": "2025-03-18T22:16:21.895Z", "updated": "2025-03-18T22:16:21.895Z", "process_date": "2025-03-18T22:16:21.895Z", "transaction_id": "67d9f0b57131be1625d301c4", "transaction_proof": "2ca560b36dcdd7c8", "__v": 0 } ], "page": 1, "balance": 3.54, "total_withdraw": -30, "total_deposit": 0}User
User account management and balance information.
/api/v2/user/balanceGet user balance
Returns the user's current balance, active balance and pending earnings.
Responses
| Status | Description |
|---|---|
| 200 | User balance information retrieved successfully |
| 401 | Unauthorized or missing token |
| 500 | Server error |
{ "balance": 4.434, "active_balance": 4.434, "pending_earnings": 1.25}Devices
Device management and information retrieval endpoints.
/api/v2/devicesGet debuggable devices
Retrieves a list of devices that are either owned by the user or rented with live payment type.
Responses
| Status | Description |
|---|---|
| 200 | Successfully retrieved devices list |
| 401 | Unauthorized or missing token |
| 500 | Server error |
[ { "remote_device_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "added": "2025-08-19T10:00:00Z", "automations": [], "brand": "Samsung", "country": "US", "isEmulator": false, "model": "Galaxy S21", "name": "Atomic Mammal", "networkType": "wifi", "numberOfCores": 8, "online": true, "processor": "Snapdragon 888", "ramMb": 8192, "sdkVersion": "31", "version": "12" }]/api/v2/devices/marketList market devices (action / online)
Returns devices that are listed on the market with payment_type 'action' and currently online. Each item includes the device name, country, and an isAvailable flag (false when the device has at least one job task in 'running' status, true otherwise). Results can be filtered by country and are sorted by country.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
country | string | No | Filter devices by country. |
sortDirection | string | No | Sort direction for the country field. "asc" or "desc". Defaults to "asc". |
Responses
| Status | Description |
|---|---|
| 200 | Successfully retrieved the list of online, action-payment market devices. |
| 401 | Unauthorized or missing token |
| 500 | Server error |
[ { "name": "Atomic Mammal", "country": "BR", "isAvailable": true }, { "name": "Enormous Elephant", "country": "US", "isAvailable": false }]/api/v2/devices/verified-phone-numberGet the device's verified phone number
Agent-scoped endpoint. Identifies the device from the automation agent token and returns the phone number stored on the device record only when phoneNumberVerified is true. Returns null otherwise. Backs `agent.info.getVerifiedPhoneNumber` in the automation bootstrap.
Responses
| Status | Description |
|---|---|
| 200 | Returns the verified phone number stored on the device record, or null when the device has no verified phone number. |
| 400 | Token is not bound to a device (e.g. user-scoped token rather than an automation agent token). |
| 401 | Unauthorized or missing token |
| 404 | Device not found |
| 500 | Server error |
{ "phoneNumber": "+14155550123"}Proxy
Manage SOCKS5/HTTP proxy credentials. A connection is one credential pair (`connectionId` + `password`) routed through one of your devices. Re-pointing the association to a different device is the IP-rotation primitive — new sessions begin exiting through the new device within ~30 s (orchestrator auth-cache TTL); in-flight sessions stay on the old device until they close. Endpoints are scoped to devices the caller owns; the admin endpoint requires the admin token.
/api/v2/devices/connections/listList my connections
All connections owned by, or currently routed through, devices the caller owns. The `password` is plaintext — RMS owns the credential.
Responses
| Status | Description |
|---|---|
| 200 | All connections owned by, or routed through, devices the caller owns. |
| 401 | Missing or invalid API token. |
{ "success": true, "connections": [ { "connectionId": "wXyZ04827193", "password": "p9F7bX3cVqLt", "ownerUserId": "add02036-6754-4cf7-8432-cc28be4468f0", "name": "client-A", "createdAt": "2026-04-25T13:22:11Z", "deviceId": "add02036-6754-4cf7-8432-cc28be4468f0" } ]}/api/v2/devices/connections/createCreate a connection
Generates a fresh `connectionId` (4 letters + 8 digits) and 12-char password, associates it with the chosen device, and syncs to the orchestrator + legacy mirror. The plaintext password is in the response — save it now.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
device_id | string (UUID — `remote_device_id`) | Yes | Owned device id. |
name | string | No | Optional human label (≤ 100 chars). Cosmetic only — the credential is the auto-generated `connectionId`. |
Responses
| Status | Description |
|---|---|
| 200 | Created — RMS generates a `connectionId` (4 letters + 8 digits) + 12-char password and pushes to the orchestrator. Save the plaintext password from the response: the orchestrator stores only a hash. |
| 400 | `device_id` missing. |
| 404 | Device not owned by caller. |
{ "success": true, "connection": { "connectionId": "wXyZ04827193", "password": "p9F7bX3cVqLt", "ownerUserId": "add02036-6754-4cf7-8432-cc28be4468f0", "name": "client-A", "createdAt": "2026-04-25T13:22:11Z", "deviceId": "add02036-6754-4cf7-8432-cc28be4468f0" }}/api/v2/devices/connections/deleteDelete a connection
Cascades on RMS, orchestrator, and the legacy mirror. Active sessions are terminated.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
connection_id | string | Yes | The connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username. |
Responses
| Status | Description |
|---|---|
| 200 | Deleted on RMS, the orchestrator, and the legacy mirror. Active proxy sessions using this credential are terminated. |
| 403 | Connection not owned/routed through any device the caller owns. |
| 404 | Connection not found. |
/api/v2/devices/connections/password/rotateRotate password
Replaces the connection's password (same `connectionId`). Active sessions continue on the old credential until they close.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
connection_id | string | Yes | The connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username. |
Responses
| Status | Description |
|---|---|
| 200 | Generates a new 12-char password (same `connectionId`). |
| 403 | Forbidden. |
{ "success": true, "password": "qK4LmPzWxR7a" }/api/v2/devices/connections/association/setMove connection to another device (IP rotation)
Hot-swap the exit device. Owner must own both the source connection and the target device.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
connection_id | string | Yes | The connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username. |
device_id | string (UUID) | Yes | Target device — **must also be owned by the caller**. |
Responses
| Status | Description |
|---|---|
| 200 | Hot-swap successful. New SOCKS5/HTTP CONNECT sessions begin routing to the new device within ~30 s (orchestrator auth-cache TTL). In-flight sessions stay on the old device until they close. |
| 403 | Source connection or target device not owned by caller. |
/api/v2/devices/connections/association/deleteRemove association
Connection stays alive but cannot authenticate until re-associated.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
connection_id | string | Yes | The connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username. |
Responses
| Status | Description |
|---|---|
| 200 | Connection stays but cannot route until a new association is set. Active sessions are terminated. |
| 403 | Forbidden. |
/api/v2/devices/connections/blacklist/listList per-connection blacklist
Per-connection block list (in addition to global + per-device).
Request body
| Name | Type | Required | Description |
|---|---|---|---|
connection_id | string | Yes | The connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username. |
Responses
| Status | Description |
|---|---|
| 200 | Per-connection blacklist domains (in addition to the global blacklist). Suffix matching applies. |
| 403 | Forbidden. |
{ "success": true, "domains": ["example.com", "tracker.io"] }/api/v2/devices/connections/blacklist/addAdd domains to blacklist
Adds one or more domains to the connection's blacklist. Suffix matching applies on the proxy side — blocking `example.com` also blocks `sub.example.com`.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
connection_id | string | Yes | The connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username. |
domains | string[] | Yes | Non-empty array of domains to add or remove. |
Responses
| Status | Description |
|---|---|
| 200 | Updated. |
| 403 | Forbidden. |
/api/v2/devices/connections/blacklist/removeRemove domains from blacklist
Removes one or more domains from the connection's blacklist.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
connection_id | string | Yes | The connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username. |
domains | string[] | Yes | Non-empty array of domains to add or remove. |
Responses
| Status | Description |
|---|---|
| 200 | Updated. |
| 403 | Forbidden. |
/api/v2/devices/connections/whitelist/listList whitelisted IPs
Returns the IP/CIDR patterns currently on the connection's whitelist. When a customer's source IP matches any pattern, they can open proxy streams without a password — just the `connectionId` in the SOCKS5/HTTP username field.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
connection_id | string | Yes | The connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username. |
Responses
| Status | Description |
|---|---|
| 200 | Array of whitelisted IP patterns. |
| 401 | Missing or invalid API token. |
{ "success": true, "ips": ["203.0.113.5", "10.0.0.0/8"]}/api/v2/devices/connections/whitelist/addAdd IP to whitelist
Adds an IP or CIDR to the connection's whitelist. Idempotent — re-posting the same pattern updates its label. Domain patterns are rejected (use blacklist for domain-level control).
Request body
| Name | Type | Required | Description |
|---|---|---|---|
connection_id | string | Yes | The connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username. |
pattern | string | Yes | IP address or CIDR to whitelist (e.g. `1.2.3.4` or `10.0.0.0/8`). Domain patterns are rejected. |
label | string | No | Optional human label (e.g. `office`, `CI runner`). |
Responses
| Status | Description |
|---|---|
| 200 | Whitelist entry added/removed. |
| 400 | Invalid pattern (must be IP or CIDR). |
| 401 | Missing or invalid API token. |
| 502 | Gost orchestrator unreachable. |
/api/v2/devices/connections/whitelist/removeRemove IP from whitelist
Removes an IP/CIDR from the connection's whitelist.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
connection_id | string | Yes | The connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username. |
pattern | string | Yes | IP or CIDR to remove from the whitelist. |
Responses
| Status | Description |
|---|---|
| 200 | Whitelist entry added/removed. |
| 400 | Invalid pattern (must be IP or CIDR). |
| 401 | Missing or invalid API token. |
| 502 | Gost orchestrator unreachable. |
/api/v2/devices/transport-mode/getGet device transport mode
Returns the current egress transport mode for the device. Determines which network interface outbound traffic exits through.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
device_id | string (UUID — `remote_device_id`) | Yes | Owned device id. |
Responses
| Status | Description |
|---|---|
| 200 | Mode returned or updated. |
| 400 | Invalid mode value. |
| 401 | Missing or invalid API token. |
| 403 | Device not owned by caller. |
| 502 | Gost orchestrator unreachable. |
/api/v2/devices/transport-mode/setSet device transport mode
Changes the egress transport mode. The phone picks up the change within one heartbeat interval (~30 s). `cellular` uses the carrier IP (default), `wifi` uses the WiFi router's public IP, `auto` prefers cellular with WiFi fallback. Note: `wifi` mode is partial — the downstream transport binding (cellular + WiFi tunnel for cost savings) requires additional phone-side work.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
device_id | string (UUID — `remote_device_id`) | Yes | Owned device id. |
mode | string | Yes | `cellular` (default — carrier IP), `wifi` (WiFi router's IP), or `auto` (prefer cellular, fallback to WiFi). |
Responses
| Status | Description |
|---|---|
| 200 | Mode returned or updated. |
| 400 | Invalid mode value. |
| 401 | Missing or invalid API token. |
| 403 | Device not owned by caller. |
| 502 | Gost orchestrator unreachable. |
/api/v2/devices/proxy-logs/listQuery request logs
One log entry per SOCKS5 / HTTP CONNECT attempt. Backed by ClickHouse; results are post-filtered to devices owned by the caller.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
from | string (ISO 8601) | Yes | Inclusive lower bound on `timestamp`. |
to | string (ISO 8601) | Yes | Inclusive upper bound on `timestamp`. |
connectionId | string | No | Filter to one connection. |
deviceId | string (UUID) | No | Filter to one device — must be owned by the caller. |
target | string | No | Substring match on the request target (`host:port`). |
action | "allowed" | "blocked" | No | Filter by outcome. |
limit | integer | No | Page size (default 100, max 10000). |
offset | integer | No | 0-indexed offset for pagination. |
backend | "zarif" | "gost" | No | Which proxy orchestrator to query. Default `zarif`. Pass in the JSON request body for POST endpoints; URL query params are ignored. |
Responses
| Status | Description |
|---|---|
| 200 | Request log rows from ClickHouse. Post-filtered to devices owned by the caller. |
| 400 | `from`/`to` missing or invalid; `limit` out of range. |
{ "success": true, "total": 12345, "entries": [ { "timestamp": "2026-04-25T13:22:11Z", "connectionId": "wXyZ04827193", "deviceId": "add02036-6754-4cf7-8432-cc28be4468f0", "serverNode": "server-us-aws", "target": "example.com:443", "targetType": "domain", "port": 443, "resolvedIp": "", "action": "allowed", "blockReason": null, "bytesIn": 184213, "bytesOut": 4218 } ]}/api/v2/devices/proxy-bandwidthQuery bandwidth aggregates
Per-(device, connection) byte counts, optionally grouped by hour or day. When `deviceId` is omitted the backend issues one query per owned device and merges.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
deviceId | string (UUID) | No | Filter to one device — must be owned by the caller. Omit to fetch per-device rows for every device the caller owns. |
connectionId | string | No | Filter to one connection. |
from | string (ISO 8601) | No | Inclusive lower bound. |
to | string (ISO 8601) | No | Inclusive upper bound. |
groupBy | "hour" | "day" | No | Time-bucket granularity. |
backend | "zarif" | "gost" | No | Which proxy orchestrator to query. Default `zarif`. Pass in the JSON request body for POST endpoints; URL query params are ignored. |
Responses
| Status | Description |
|---|---|
| 200 | Aggregated bandwidth rows from the orchestrator. |
{ "success": true, "rows": [ { "deviceId": "add02036-6754-4cf7-8432-cc28be4468f0", "connectionId": "wXyZ04827193", "bytesIn": 152841234, "bytesOut": 9183748, "bucket": "2026-04-25T13:00:00Z" } ]}/api/v2/admin/devices/proxiesList all proxy connections (admin)
Returns every proxy connection across all users, joined with the associated device's metadata and the orchestrator's bandwidth aggregates (24h + lifetime). Requires the admin API token (`X-Admin-Token`). `device` is `null` when the connection has no current association.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
backend | "zarif" | "gost" | No | Which proxy orchestrator to read aggregates from (URL query string, since this endpoint is GET). Default `zarif`. |
Responses
| Status | Description |
|---|---|
| 200 | Connection list with associated device + bandwidth. |
| 400 | Unknown `backend` value. |
| 401 | Missing or invalid admin API token. |
| 502 | Could not reach the proxy orchestrator. |
| 503 | Proxy orchestrator / RMS not configured (env vars missing). |
{ "total": 2, "proxies": [ { "connection_id": "wXyZ04827193", "name": "client-A", "password": "p9F7bX3cVqLt", "created_at": "2026-04-25T13:22:11Z", "bytes_last_24h": 152841234, "bytes_lifetime": 9183748192, "device": { "remote_device_id": "add02036-6754-4cf7-8432-cc28be4468f0", "user_id": "65f3a1c0d4e2f10012abcd34", "name": "Atomic Mammal", "brand": "Samsung", "model": "SM-G998B", "country": "US", "online": true, "is_rented": false, "is_onMarket": false } }, { "connection_id": "aBcD19384756", "name": null, "password": "qK4LmPzWxR7a", "created_at": "2026-04-25T13:22:12Z", "bytes_last_24h": 0, "bytes_lifetime": 24576, "device": null } ]}Bucket
Per-(job, device) JSON bucket — the persistent state automations attach to a single hire on a single device. The endpoints below are the **employer-facing** surface, gated by job ownership / share permission. They mirror the same model as the **Buckets** tab on the Job details page. (The agent surface used from inside automations is `agent.utils.bucket` — see the automation docs.)
/api/v2/bucket/:job_idList buckets for a job
Returns a paginated list of every bucket attached to this job, with the device display name resolved for each.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string (ObjectId) | Yes | Path param. The job's `job_id`. Caller must own the job, or have it shared to them (`view` for reads, `edit` for writes/deletes). |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | integer | No | 1-indexed page. Defaults to `1`. |
limit | integer | No | Page size. Defaults to `20`. Capped at `100`. |
Responses
| Status | Description |
|---|---|
| 200 | Paginated list of buckets for this job (newest first by `updated_at`). Each entry is enriched with the device's display name. |
| 404 | Job not found, or caller does not own / does not have the required share permission (`view` for reads, `edit` for writes / deletes).{ "error": "Job not found" } |
| 500 | Unexpected DB error.{ "error": "Internal server error" } |
{ "buckets": [ { "_id": "65f1...", "job_id": "65aa...", "remote_device_id": "8b2f-uuid", "data": { "sessionToken": "abc123", "loggedIn": true }, "updated_at": "2026-04-15T08:14:00.000Z", "device_name": "Pixel 5 — Lab" } ], "total": 1, "page": 1, "pages": 1}/api/v2/bucket/:job_idDelete all buckets for a job
Wipes every bucket associated with this job. Caller must have `edit` access (owner or shared-edit).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string (ObjectId) | Yes | Path param. The job's `job_id`. Caller must own the job, or have it shared to them (`view` for reads, `edit` for writes/deletes). |
Responses
| Status | Description |
|---|---|
| 200 | All buckets for this job removed. Returns the count deleted.{ "success": true, "deleted": 12 } |
| 404 | Job not found, or caller does not own / does not have the required share permission (`view` for reads, `edit` for writes / deletes).{ "error": "Job not found" } |
| 500 | Unexpected DB error.{ "error": "Internal server error" } |
/api/v2/bucket/:job_id/:device_idRead a single bucket
Returns the bucket for this `(job_id, device_id)` pair. 404 when none exists or the job is inaccessible.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string (ObjectId) | Yes | Path param. The job's `job_id`. Caller must own the job, or have it shared to them (`view` for reads, `edit` for writes/deletes). |
device_id | string (UUID) | Yes | Path param. Device's `remote_device_id`. |
Responses
| Status | Description |
|---|---|
| 200 | Returns the bucket document for this `(job_id, device_id)` pair. |
| 404 | Either the job is inaccessible to the caller, or no bucket exists for this device.{ "error": "Bucket not found" } |
| 500 | Unexpected DB error.{ "error": "Internal server error" } |
{ "bucket": { "_id": "65f1...", "job_id": "65aa...", "remote_device_id": "8b2f-uuid", "data": { "sessionToken": "abc123", "loggedIn": true }, "updated_at": "2026-04-15T08:14:00.000Z" }}/api/v2/bucket/:job_id/:device_idWrite a bucket
Replaces the bucket payload for this `(job_id, device_id)` pair (no merge — `data` becomes the new bucket). Validated against the automation's `bucket_schema` when one is defined. Caller must have `edit` access.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string (ObjectId) | Yes | Path param. The job's `job_id`. Caller must own the job, or have it shared to them (`view` for reads, `edit` for writes/deletes). |
device_id | string (UUID) | Yes | Path param. Device's `remote_device_id`. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
data | object | Yes | New bucket payload. Replaces the existing object (no merge). If the linked automation defines a `bucket_schema`, `data` is validated against it. |
Responses
| Status | Description |
|---|---|
| 200 | Bucket created or replaced for this `(job_id, device_id)` pair. Returns the saved document. |
| 400 | `data` missing / not an object, or schema validation against the automation's `bucket_schema` failed.{ "error": "Invalid bucket data" } |
| 404 | Job not found, or caller does not own / does not have the required share permission (`view` for reads, `edit` for writes / deletes).{ "error": "Job not found" } |
| 500 | Unexpected DB error.{ "error": "Internal server error" } |
/api/v2/bucket/:job_id/:device_idDelete a bucket
Removes the bucket for this `(job_id, device_id)` pair. Caller must have `edit` access.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
job_id | string (ObjectId) | Yes | Path param. The job's `job_id`. Caller must own the job, or have it shared to them (`view` for reads, `edit` for writes/deletes). |
device_id | string (UUID) | Yes | Path param. Device's `remote_device_id`. |
Responses
| Status | Description |
|---|---|
| 200 | Bucket for this `(job_id, device_id)` pair removed.{ "success": true } |
| 404 | Job not found, or caller does not own / does not have the required share permission (`view` for reads, `edit` for writes / deletes).{ "error": "Job not found" } |
| 500 | Unexpected DB error.{ "error": "Internal server error" } |
Device Bucket
Per-device JSON bucket, shared across every job and automation running on the device. Useful for cross-job state — login cookies, account tokens, app config. Distinct from the per-job bucket: there is no `bucket_schema` validation, and no job context is required, so direct-run automations can use it too. The agent calls these via `agent.utils.deviceBucket`.
/api/v2/device-bucketRead device bucket
Reads the device-scoped bucket. The `remote_device_id` is taken from the token when it carries a device context; non-agent callers must supply it in the body.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
remote_device_id | string (UUID) | No | The device's `remote_device_id`. Optional when the token already carries a device context (agent tokens). Required for plain Client API tokens. Must point to a device the caller owns or is currently running on. |
Responses
| Status | Description |
|---|---|
| 200 | Returns the device-scoped bucket. `deviceBucket` is `{}` on first read. |
| 400 | Token has no device context and `remote_device_id` was not supplied.{ "error": "Device bucket requires a device context (remote_device_id)" } |
{ "deviceBucket": { "accountToken": "xyz", "username": "user@example.com" }}/api/v2/device-bucket/setMerge into device bucket
Merges the supplied object into the existing device bucket. Existing keys not in the payload are preserved.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
remote_device_id | string (UUID) | No | The device's `remote_device_id`. Optional when the token already carries a device context (agent tokens). Required for plain Client API tokens. Must point to a device the caller owns or is currently running on. |
data | object | Yes | Non-null, non-array JSON object. Top-level keys are merged with the stored bucket — existing keys not in the payload are preserved. |
Responses
| Status | Description |
|---|---|
| 200 | Bucket merged and persisted. Returns the full merged object. |
| 400 | Missing device context or `data` is missing / null / an array / not an object.{ "error": "Invalid data. Must be a non-null object." } |
{ "success": true, "deviceBucket": { "accountToken": "xyz", "username": "user@example.com" }}Other
File upload and temporary file access endpoints.
/api/v2/files/uploadUpload temporary file
Uploads a file to temporary storage. Files are automatically deleted after 15 minutes. Maximum file size is 50 MB. Use multipart/form-data with 'file' field name.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
file | file | Yes | File to upload (multipart/form-data) |
Responses
| Status | Description |
|---|---|
| 200 | File uploaded successfully{ "success": true, "message": "File uploaded successfully", "data": { "filename": "1234567890_example.pdf", "originalName": "example.pdf", "size": 1024000, "url": "https://api.example.com/temp/1234567890_example.pdf", "expiresAt": "2025-10-08T12:15:00.000Z" } } |
| 400 | No file uploaded{ "success": false, "message": "No file uploaded" } |
| 401 | Unauthorized or missing tokenAuthentication error message |
| 500 | Server error during upload{ "success": false, "message": "Error uploading file", "error": "Error details" } |
{ "success": true, "message": "File uploaded successfully", "data": { "filename": "1234567890_example.pdf", "originalName": "example.pdf", "size": 1024000, "url": "https://api.example.com/temp/1234567890_example.pdf", "expiresAt": "2025-10-08T12:15:00.000Z" }}/api/v2/files/temp/:filenameGet uploaded file
Retrieves a previously uploaded temporary file. Files expire after 15 minutes from upload time.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
filename | string | Yes | Filename returned from the upload endpoint (path parameter) |
Responses
| Status | Description |
|---|---|
| 200 | File retrieved successfullyBinary file data |
| 400 | Filename is required{ "success": false, "message": "Filename is required" } |
| 404 | File not found or expired{ "success": false, "message": "File not found or expired" } |
| 401 | Unauthorized or missing tokenAuthentication error message |
| 500 | Server error during retrieval{ "success": false, "message": "Error retrieving file", "error": "Error details" } |