Xgodo API Documentation

API Reference for Jobs, Tasks, Payments, and User Management

LLM / automated agent notice: a complete, plain-text reference of every v2 Client API endpoint and the automation agent docs — generated from the same source as these pages — is available at /docs/llm.txt. Prefer that single file over fetching each section page individually; it is both smaller and easier to parse.

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 API

Manage job postings, retrieve applicants, and handle job-related operations.

POST/api/v2/jobs/applicants
Retrieve 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 Parameters

ParameterTypeRequiredDescriptionExample
job_idstringYesThe ID of the job for which to retrieve applicants-
task_idstringNoThe 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.-
sortbystringNoField by which to sort job applicants-
orderstringNoSort order. Can be 'asc' for ascending or 'desc' for descendingasc
pageintegerNoPage number for pagination1
limitintegerNoNumber of applicants per page10
hours_agointegerNoThe day interval, specified as the number of days starting from the given hours_ago up to today10
statusstringNoThe status of the task ('processing', 'confirmed', 'notcomplete', 'declined', 'pending')confirmed
searchstringNoSearch term to filter applicants based on worker name, job proof, job title, comment, or worker IPdeveloper

Responses

Status CodeDescriptionResponse Body
200Successfully 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
400Invalid 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)"}
404Job 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"}
PUT/api/v2/jobs/applicants
Update 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. By default (stopRunningAutomation=true) tasks that are still `running` are included: their automation is stopped on the device before the status is applied. Optionally accepts result_types ({ [job_task_id]: { [field]: value } }): sets or overrides the recorded result types of the listed unreviewed tasks — strictly validated against the job's result_type_pricing — re-deriving each task's price (max(0, job_price + sum of the selected values' deltas)) and storing a price_breakdown snapshot before the payout is computed. Already-confirmed tasks are never affected.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
result_typesobjectNoMap of job_task_id to a result type map ({field: value}). Validated against the job's result_type_pricing; re-derives each listed task's price and stores its price_breakdown before payout. Only unreviewed tasks can be targeted.{"686f668db9c27eea026e60c7": {"Account Type": "Old Account"}}
JobTasks_IdsarrayYesAn 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.["60d21b4667d0d8992e610c85", "60d21b4667d0d8992e610c86"]
statusstringYesThe new status to set for the job tasksconfirmed
job_idstringYesThe ID of the job to which the tasks belong60d21b4667d0d8992e610c85
commentstringNoOptional comment to add regarding the task updateTask successfully completed.
stopRunningAutomationbooleanNoDefaults to true. When true, tasks that are currently `running` are also updated: their live automation on the device is stopped (iteration cleared + device lock acquired, then an RMS stop is dispatched in the background — same as the timeout path) and the requested status is applied. When false, `running` tasks are skipped as before. `confirmed` tasks are always excluded.true

Responses

Status CodeDescriptionResponse Body
200Successfully updated job task statuses.Returns the updated job task details
400Bad 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)"}
404Job 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"}
GET/api/v2/jobs/details
Get 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

ParameterTypeRequiredDescriptionExample
job_idstringNoUnique identifier for the job68910c4a5e1c7092a1fdc03a

Example Response

{
  "_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 }
    ]
  }
}
POST/api/v2/jobs/check-uniquenes
Check 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 Parameters

ParameterTypeRequiredDescriptionExample
searchstringYesThe term to search for in job proofsdeveloper
optionstringNoThe search option to use. Can be "word" for exact word matching or "charactor" for substring matchingword

Responses

Status CodeDescriptionResponse Body
200Successfully checked for uniquenessReturns a boolean indicating if the search term is unique
400Bad request. Either the search term is missing, or an invalid search option is providedError message describing the validation error
POST/api/v2/jobs/myjobs
Retrieve 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 Parameters

ParameterTypeRequiredDescriptionExample
pageintegerNoPage number for the jobs you posted (the `data` list)1
limitintegerNoNumber of items per page (applies to both lists; default 10, max 200)10
shared_pageintegerNoPage number for the jobs shared with you (the `shared_data` list). Paginates independently of `page`. Defaults to 1.1
job_idstringNoFilter jobs by a specific job ID-
statusstringNoFilter jobs by status (e.g., 'active', 'completed')-
orientationstringNoThe orientation for the data sorting (e.g., 'asc' or 'desc')asc
QuerystringNoSearch query for job title or description-
SortBYstringNoField to sort the results by-
POST/api/v2/jobs/submit
Create 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 Parameters

ParameterTypeRequiredDescriptionExample
titlestringYesThe title of the job postingSoftware Developer
categorystringYesCategory of the jobEngineering
descriptionstringYesDetailed description of the jobWe are looking for a skilled software developer...
proofstringYesProof or evidence related to the job postingSample proof text.
is_proof_filebooleanNoIndicates if the proof is a file or textfalse
auto_ratebooleanNoWhether the job should be automatically ratedtrue
vcodestringNoVerification code for the job postingcdcdc
Target_WorkersarrayNoList of target workers or countries["worldwide"]
few_timesbooleanNoIndicates if the job can be posted a few timesfalse
premiumbooleanNoIndicates if the job is premiumfalse
featuredbooleanNoWhether the job is featured or notfalse
durationintegerYesDuration for which the job is posted (in days)60
positionsintegerYesNumber of available positions for the job3
daily_limitintegerNoDaily limit for the job posting0
job_pricenumberYesPrice for posting the job100

Responses

Status CodeDescriptionResponse Body
201Job created successfullySuccess message indicating job creation
400Bad request. Invalid or missing required fieldsList of validation error messages
PUT/api/v2/jobs/update-status
Update job status

Updates the status of a specific job.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
job_idstringYesThe ID of the job60c72b2f9b1e8c001c8e4d8a
statusstringYesNew status for the job (active, inactive)active

Responses

Status CodeDescriptionResponse Body
204Job status updated successfully-
400Invalid request body or status-
401Unauthorized action-
404Job not found or invalid job_id-

Tasks API

Manage job tasks, apply for tasks, and submit task completions.

GET/api/v2/tasks/apply
Apply 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

ParameterTypeRequiredDescriptionExample
job_idstringYesUnique identifier for the job68868530c189957861cd698a

Example Response

{
  "_id": "68910c4a5e1c7092a1fdc03c",
  "var1": "Custom variable value 1",
  "var2": "Custom variable value 2"
}
POST/api/v2/tasks/details
Get recent tasks or details of a specific job task

Returns the list of recent tasks of the user, optionally filtered by job_id and by a search over job_proof. If task_id query param is provided, it will return details of a single task (search is ignored).

Query Parameters

ParameterTypeRequiredDescriptionExample
task_idstringNoThe ID of the job task to fetch details for686f668db9c27eea026e60c7

Request Body Parameters

ParameterTypeRequiredDescriptionExample
pageintegerNoPage number for pagination (starts from 1)1
limitintegerNoNumber of tasks to return per page10
job_idstringNoOnly return tasks of this job. The counts in the response are scoped to it as well.68868530c189957861cd698a
searchstringNoOnly return tasks whose job_proof contains this text (case-insensitive substring match, max 500 characters). The counts in the response are filtered by it as well. Tasks whose proof is hidden from you (automation tasks your device ran for jobs you don't own) never match. Searching a regular job by job_id uses the proof search index, which is updated within moments of a submission, so a task submitted just now may not match yet.user@example.com

Example Response

{
  "_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": []
}
POST/api/v2/tasks/submit
Submit 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, decline_ban, Worker_Invites) and triggers task/job completion emails. The decline_ban gate is evaluated on every submission: if the job configures decline_ban, the caller is rejected while serving a ban, and is banned (and rejected) when at least min_tasks of their tasks on that job were reviewed within the last window_minutes and at least decline_rate_percent% of those were declined. Only reviewed tasks (confirmed / declined) count, measured by review time — tasks still awaiting review are ignored. 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 Parameters

ParameterTypeRequiredDescriptionExample
job_idstringYesID 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.68868530c189957861cd698a
job_proofstringYesProof of the task done. In automation data-dump mode this string MUST be valid JSON.irjfoirf@gmail.com:iejowiedj:ehiuehwd@outlook.com:wuehuidhewd
custom_varsobjectNoManual mode only. Custom vars obtained from tasks/apply. Required for jobs with variables.{"_id": "68910c4a5e1c7092a1fdc03c", "var1": "value1"}
proof_files_base64arrayNoManual mode only. Array of required and optional proof files in base64 format, mapped to the job's predeclared file fields.[{"fieldName": "screenshot", "fileName": "image.jpg", "base64": "..."}]
statusstringNoAutomation data-dump mode only. One of "pending", "failed", or "declined". Defaults to "pending".pending
filesarrayNoAutomation 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.[{"name": "screenshot", "extension": ".png", "base64Data": "..."}]
result_typeobjectNoAutomation data-dump mode only. Result type map ({field name: selected value}), strictly validated against the shared automation's result_type_schema (unknown fields or disallowed values are rejected; omitted fields fall back to their schema defaults). Stored on the created task, and the task's price is derived from the TARGET job's result_type_pricing deltas: max(0, job_price + sum of each recorded value's delta), with a price_breakdown snapshot recorded on the task.{"Account Type": "Old Account"}

Responses

Status CodeDescriptionResponse Body
201Job task submitted successfullySuccess message indicating task submission, job task id and job id
400Bad request — e.g. job_proof is not a string, or in automation data-dump mode job_proof is not valid JSON / files validation failed. Also returned in manual mode when a rate limit is reached, or when the target job's decline_ban rule bans the caller (either they are already serving a ban, or this submission trips the rule).Error message describing the validation failure
403Forbidden. 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
404Job not found, running task's job not found, or device not foundError message indicating the missing resource
POST/api/v2/tasks/setResultType
Set the result type of a running task

Lets an automation agent report the result type of its current task (e.g. Account Type = "Old Account" vs "New Account"). The map is strictly validated against the result_type_schema defined on the automation by its developer, and the task's price snapshot is re-derived from the job's employer-defined result_type_pricing: every value carries a price DELTA relative to the default job_price, and the task price becomes max(0, job_price + sum of the recorded values' deltas). A price_breakdown snapshot (base, per-value adjustments, total) is stored on the task so the price stays explainable even if the job's pricing is edited later. Only allowed while the task is still running — once the task finishes, its price is frozen for review and payout. Can be called multiple times; the last value before finishing wins. The agent never sends a price, only a schema-validated combination. Also available as agent.utils.job.setResultType() in automation scripts, or via the resultType parameter of agent.utils.job.submitTask().

Request Body Parameters

ParameterTypeRequiredDescriptionExample
task_idstringYesThe ID of the currently running job task (the agent token's jobTaskId)686f668db9c27eea026e60c7
result_typeobjectYesResult type map ({field name: selected value}). Strictly validated against the automation's result_type_schema: unknown fields or values not in the allowed list are rejected. Omitted fields fall back to their schema defaults.{"Account Type": "Old Account"}

Responses

Status CodeDescriptionResponse Body
200Result type stored on the task. The response includes the normalized result type (defaults filled in), the derived per-task price (max(0, job_price + sum of each value's delta from the job's result_type_pricing)), and the price_breakdown snapshot recorded on the task.{"success": true, "result_type": {"Account Type": "Old Account"}, "job_price": 0.5}
400Invalid task id, the automation defines no result_type_schema, the result_type contains an unknown field or disallowed value, or the task is no longer running (the task price is frozen once the task finishes).{"error": "Invalid value for result type field \"Account Type\". Allowed: Old Account, New Account."}
404Task not found or not owned by the calling worker{"error": "Task not found or access denied"}
POST/api/v2/tasks/getExtras
Get the extras of a job task

Returns the free-form extras object of a job task owned by the calling worker. A job task starts with the extras of the planned task it was created from. Also available as agent.utils.job.getExtras() in automation scripts.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
task_idstringYesThe ID of the job task. With an agent token it must be the token's own jobTaskId.686f668db9c27eea026e60c7

Responses

Status CodeDescriptionResponse Body
200The task's extras object (null when it has none).{"success": true, "extras": {"campaign": "spring"}}
400Invalid task id{"success": false, "error": "Invalid task id"}
404Task not found, not owned by the calling worker, or not the agent token's own task{"success": false, "error": "Task not found or access denied"}
POST/api/v2/tasks/setExtras
Set the extras of a running job task

Writes the free-form extras object of a running job task owned by the calling worker. By default the keys are merged into the existing extras; pass merge: false to replace the whole object. Only allowed while the task is running. Also available as agent.utils.job.setExtras() in automation scripts.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
task_idstringYesThe ID of the currently running job task. With an agent token it must be the token's own jobTaskId.686f668db9c27eea026e60c7
extrasobjectYesKeys to write. Top-level keys must match [A-Za-z0-9_]{1,64}; values can be any JSON. The resulting extras object must be at most 10,000 bytes as JSON.{"email": "user@example.com"}
mergebooleanNotrue (default) merges the keys into the existing extras, keeping other keys. false replaces the whole extras object.true

Responses

Status CodeDescriptionResponse Body
200Extras written. Returns the task's extras after the write.{"success": true, "extras": {"campaign": "spring", "email": "user@example.com"}}
400Invalid task id, invalid extras key, extras over 10,000 bytes, or the task is no longer running{"success": false, "error": "extras can only be set while the task is running"}
404Task not found, not owned by the calling worker, or not the agent token's own task{"success": false, "error": "Task not found or access denied"}
409The extras kept changing during the write; retry{"success": false, "error": "extras were modified concurrently, please retry"}
GET/api/v2/planned_tasks
List 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

ParameterTypeRequiredDescriptionExample
job_idstringYesThe ID of the job whose planned tasks should be listed60c72b2f9b1e8c001c8e4d8a
searchstringNoCase-insensitive substring match against the planned task input (max 10,000 chars)john@example.com
pageintegerNoPage number for pagination (starts from 1). Defaults to 11
limitintegerNoNumber of planned tasks to return per page (1-100). Defaults to 1010
sortOrderstringNoSort order by creation time. One of "asc" or "desc". Defaults to "desc"desc

Responses

Status CodeDescriptionResponse Body
200Planned 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": "...", "extras": {"campaign": "summer"}, "added": "..."}], "total": 42}}
400Bad request - Invalid query parameters{"success": false, "error": "Invalid job id"}
404Job not found or user doesn't own the job{"success": false, "error": "Job not found"}
500Internal server error{"success": false, "message": "Internal Server Error"}
POST/api/v2/planned_tasks/submit
Submit 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 Parameters

ParameterTypeRequiredDescriptionExample
job_idstringYesThe ID of the job to submit planned tasks for60c72b2f9b1e8c001c8e4d8a
inputsarrayYesArray of input strings for planned tasks (usually JSON strings, minimum 1, maximum 10,000 characters per string)["{\"fname\":\"John\",\"lname\":\"Doe\",\"email\":\"john@example.com\"}", "{\"fname\":\"Jane\",\"lname\":\"Smith\",\"email\":\"jane@example.com\"}"]
extrasobject | (object|null)[]NoFree-form JSON object stored on each created planned task and automatically copied onto the job task created against it. Pass a single object to apply it to every input, or an array with the same length as inputs (entries may be null) for per-input extras. Each object must serialize to at most 10,000 bytes of JSON.{"campaign": "summer", "batch": 3}
remote_device_idstring (UUID v4)NoOptional 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.a1b2c3d4-e5f6-7890-abcd-ef1234567890
device_namestringNoOptional 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.Atomic Mammal
run_immediatelybooleanNoIf 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. A task also counts as assigned when a running $0 task of an interruptible job is being stopped to free a device for it; it starts within seconds, once the stop completes. Defaults to false.true
screen_recordingboolean | nullNoPer-task screen recording override, stored on each created planned task. true records the task regardless of the job's Screen Recording settings: the enabled flag, sampling and every per-device / job limit are bypassed (the device must still be able to capture without a consent dialog and the recording store must have space; otherwise the task's recording row is skipped with the reason). false never records the task. Omit or pass null to let the job settings decide.true

Responses

Status CodeDescriptionResponse Body
200Planned 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}]}
400Bad 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."}
404Job not found or user doesn't own the job{"success": false, "error": "Job not found"}
500Internal server error{"success": false, "message": "Internal Server Error"}
DELETE/api/v2/tasks/delete
Delete a job task by ID

This endpoint allows employers to delete a job task. The task must belong to a job owned by (or shared with edit permission to) the caller. 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) — plus running, when stopRunningAutomation is true (default), in which case the live automation is stopped on the device before deletion. 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. Deleting does not reset the job's rate limits: for device-automation jobs the deleted task is archived and still counts toward the per-device, per-user, global and lifetime limits. Tasks declined because the agent failed to start never count toward limits, live or deleted.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
task_idstringYesThe 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.60c72b2f9b1e8c001c8e4d8a
keep_planned_taskbooleanNoIf 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.false
stopRunningAutomationbooleanNoDefaults to true. When true, a task with status `running` also becomes deletable: its live automation on the device is stopped (iteration cleared + device lock acquired, then an RMS stop is dispatched in the background — same as the timeout path) before the task is deleted. When false, `running` tasks stay undeletable (only failed/declined/confirmed).true

Responses

Status CodeDescriptionResponse Body
200Job 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"}
400Bad 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 'processing'. Only tasks with status: failed, declined, confirmed, running can be deleted."} or {"success": false, "error": "Planned task {id} exists but has no associated job task (job_task_id is null)"}
403Forbidden - User doesn't have permission to delete this job task{"success": false, "message": "You don't have permission to delete this job task"}
404Job 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"}
500Internal server error{"success": false, "message": "Internal Server Error"}
DELETE/api/v2/planned_tasks
Delete 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 Parameters

ParameterTypeRequiredDescriptionExample
planned_task_idsstring[]YesArray 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.["60c72b2f9b1e8c001c8e4d8a", "60c72b2f9b1e8c001c8e4d8b"]

Responses

Status CodeDescriptionResponse Body
200Planned tasks deleted successfully{"success": true}
400Bad 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"}
404One or more planned tasks not found, or the job is not owned by the caller{"success": false, "message": "Planned task not found"}
500Internal server error{"success": false, "message": "Internal Server Error"}
POST/api/v2/planned_tasks/reinitiate
Re-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. The replaced job tasks are archived and still count toward the job's per-device, per-user, global and lifetime limits (tasks declined because the agent failed to start never count), so the device that just failed waits out the interval before it can pick the task up again.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
task_idstringNoThe ID of a single job task to re-initiate (for backward compatibility)60c72b2f9b1e8c001c8e4d8a
task_idsstring[]NoAn array of job task IDs to re-initiate. Either task_id or task_ids must be provided.["60c72b2f9b1e8c001c8e4d8a", "60c72b2f9b1e8c001c8e4d8b"]

Responses

Status CodeDescriptionResponse Body
200Task re-initiated successfully{"success": true, "message": "Task successfully reinitiated"}
400Bad 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."}
403Forbidden - User doesn't have permission to reinitiate this task{"success": false, "message": "You don't have permission to reinitiate this task"}
404Job task not found or no planned task associated with this job task{"success": false, "message": "Job task not found"}
500Internal server error{"success": false, "message": "Internal Server Error"}

Payments API

Handle payment operations including withdrawals.

POST/api/v2/payments/withdraw
Withdraw user balance

Initiate a withdrawal to a wallet address.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
amountnumberYesAmount to withdraw10
wallet_addressstringYesWallet address to receive the withdrawal0x1234567890abcdef

Responses

Status CodeDescriptionResponse Body
200Withdrawal successful{"msg": "withdraw done"}
400Insufficient balance, below minimum payout, or invalid requestError message with specific reason
401Unauthorized or missing token-
500Server error-
GET/api/v2/payments/withdrawals
Get recent withdrawal details

Returns the list & details of recent withdrawals made by the user.

Query Parameters

ParameterTypeRequiredDescriptionExample
pageintegerNoPage number for pagination (starts from 1)1
limitintegerNoNumber of withdrawals to return per page10

Responses

Status CodeDescriptionResponse Body
200Details of the withdrawals made by the user-
401Unauthorized or missing token-
404No transactions found-
500Server error-

Example Response

{
  "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 API

User account management and balance information.

GET/api/v2/user/balance
Get user balance

Returns the user's current balance, active balance and pending earnings.

Responses

Status CodeDescriptionResponse Body
200User balance information retrieved successfully-
401Unauthorized or missing token-
500Server error-

Example Response

{
  "balance": 4.434,
  "active_balance": 4.434,
  "pending_earnings": 1.25
}

Devices API

Device management and information retrieval endpoints.

GET/api/v2/devices
Get debuggable devices

Retrieves a list of devices that are either owned by the user or rented with live payment type.

Responses

Status CodeDescriptionResponse Body
200Successfully retrieved devices list-
401Unauthorized or missing token-
500Server error-

Example Response

[
  {
    "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"
  }
]
GET/api/v2/devices/market
List market devices (action / online)

Returns devices that are listed on the market with payment_type 'action', currently online and not under an active automation ban. Each item includes the device id (remote_device_id — the same identifier every other device endpoint takes), the device name, country, and an isAvailable flag (false when the device has at least one job task in 'running' status, true otherwise; when job_id names a job priced above $0, running $0 tasks don't count, since a paid task can interrupt them). Results can be filtered by country and are sorted by country.

Query Parameters

ParameterTypeRequiredDescriptionExample
countrystringNoFilter devices by country.US
sortDirectionstringNoSort direction for the country field. "asc" or "desc". Defaults to "asc".asc
job_idstringNoCompute isAvailable for this job. When the job's price is above $0, a device whose only running tasks are $0 counts as available, since a paid task can interrupt them. Without it (or for a $0 job), any running task makes the device unavailable. 400 if invalid, 404 if the job doesn't exist.6a80762ceea67b6bd7ffdf3d

Responses

Status CodeDescriptionResponse Body
200Successfully retrieved the list of online, action-payment market devices.-
400job_id is not a valid id-
401Unauthorized or missing token-
404job_id doesn't match a job-
500Server error-

Example Response

[
  {
    "remote_device_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Atomic Mammal",
    "country": "BR",
    "isAvailable": true
  },
  {
    "remote_device_id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
    "name": "Enormous Elephant",
    "country": "US",
    "isAvailable": false
  }
]
GET/api/v2/devices/verified-phone-number
Get 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 CodeDescriptionResponse Body
200Returns the verified phone number stored on the device record, or null when the device has no verified phone number.-
400Token is not bound to a device (e.g. user-scoped token rather than an automation agent token).-
401Unauthorized or missing token-
404Device not found-
500Server error-

Example Response

{
  "phoneNumber": "+14155550123"
}
GET/api/v2/devices/agent-device-info
Get the device's server-side status

Agent-scoped endpoint. Identifies the device from the automation agent token (or, for a token not bound to a device, from remote_device_id) and returns the server's last-known status of it: battery level and charging, online/network, ADB and media-projection state, screen lock, notification access, XGODO keyboard enabled/active, signal, memory, storage, power-save mode, granted/missing permissions, and whether a device password is saved (never the password itself). Values are what the app last reported, not live reads. Backs `agent.info.getDeviceInfoFromServer` in the automation bootstrap.

Query Parameters

ParameterTypeRequiredDescriptionExample
remote_device_idstringNoThe device to read, for tokens that aren't bound to a device (e.g. an account API key used by a manual agent run). Must be a device the caller owns or rents with live payment. Ignored when the token is already bound to a device.8fca9f3f-3206-4f6b-9778-fd80c0e9e963

Responses

Status CodeDescriptionResponse Body
200Returns `device`: the server's last-known status of the device the agent token is bound to. Values are what the app last reported (see the *UpdatedAt fields); fields the app hasn't reported are null.-
400Token is not bound to a device and no remote_device_id was given.-
401Unauthorized or missing token-
404Device not found, or (with remote_device_id) not owned or live-rented by the caller-
500Server error-

Example Response

{
  "device": {
    "remote_device_id": "8fca9f3f-3206-4f6b-9778-fd80c0e9e963",
    "name": "Christian Pinniped",
    "status": "started",
    "brand": "Xiaomi",
    "model": "Redmi Note 7 Pro",
    "sdkVersion": 29,
    "processor": "Qualcomm Technologies, Inc SM6150",
    "numberOfCores": 8,
    "ramMb": 5740,
    "country": "in",
    "isEmulator": false,
    "version": "3.9.2",
    "versionCode": 30024,
    "online": true,
    "statusUpdatedAt": 1793612400000,
    "networkType": "wifi",
    "batteryLevel": 87,
    "batteryUpdatedAt": 1793612400000,
    "isCharging": true,
    "isAssistant": false,
    "isAdbConnected": true,
    "isMediaProjectionActive": false,
    "mediaProjectionUpdatedAt": null,
    "isScreenLockSet": true,
    "screenLockUpdatedAt": "2026-11-02T09:00:00.000Z",
    "notificationAccessGranted": false,
    "notificationListenerConnected": false,
    "deviceLanguage": "en-IN",
    "deviceTimezone": "Asia/Kolkata",
    "signalLevel": 4,
    "signalDbm": -52,
    "carrierName": "Jio",
    "availableRamMb": 2210,
    "isLowMemory": false,
    "availableStorageMb": 40312,
    "totalStorageMb": 109521,
    "isPowerSaveMode": false,
    "xgodoKeyboardEnabled": true,
    "xgodoKeyboardActive": false,
    "deviceStatsUpdatedAt": "2026-11-02T09:00:00.000Z",
    "granted_permissions": ["Accessibility", "NotificationAccess"],
    "missing_permissions": [],
    "permissions_updated_at": "2026-11-02T09:00:00.000Z",
    "has_device_password": true,
    "device_password_check_result": "supported",
    "device_password_check_at": "2026-11-02T09:00:00.000Z"
  }
}
POST/api/v2/devices/pairing-pin
Create a device pairing PIN

Allocates a fresh 8-digit pairing PIN bound to the caller's account, valid for 10 minutes. Open the XGODO app on the phone (e.g. via an https://xgodo.com/xlink?pin=… deep link) and the app redeems the PIN through RMS, registering-if-needed and signing the device into the caller's account. Rate limited per user.

Responses

Status CodeDescriptionResponse Body
201A fresh pairing PIN was allocated.-
401Unauthorized or missing token-
429Too many PIN requests (rate limited).-
503Could not allocate a PIN, retry.-

Example Response

{
  "success": true,
  "pin": "48213705",
  "expiresIn": 600
}
GET/api/v2/devices/status
Get device onboarding status

Returns onboarding/connection status for every device the caller owns: whether the agent is connected (online), the assistant/ADB capability, whether the device phone number is verified, the best-effort granted/missing Android permission lists, and market-readiness (can_publish + missing_for_market), plus optional_missing_permissions (optional AppPermission ids worth offering, e.g. NotificationAccess, or XgodoKeyboard on Android 12 and below; never blocking). Pass remote_device_id to narrow to one device.

Query Parameters

ParameterTypeRequiredDescriptionExample
remote_device_idstringNoNarrow the response to a single device. Omit for all owned devices.a1b2c3d4-e5f6-7890-abcd-ef1234567890

Responses

Status CodeDescriptionResponse Body
200Onboarding/connection status for every device owned by the caller.-
401Unauthorized or missing token-
500Server error-

Example Response

{
  "success": true,
  "devices": [
    {
      "remote_device_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "Atomic Mammal",
      "brand": "Samsung",
      "model": "Galaxy S21",
      "online": true,
      "agent_connected": true,
      "is_onMarket": false,
      "is_rented": false,
      "is_inuse": false,
      "payment_type": "action",
      "isAssistant": true,
      "isAdbConnected": false,
      "phoneNumberVerified": true,
      "granted_permissions": ["Accessibility", "UsageAccess", "Phone", "Sms"],
      "missing_permissions": [],
      "permissions_updated_at": "2026-07-22T10:00:00Z",
      "can_publish": true,
      "missing_for_market": [],
      "optional_missing_permissions": []
    }
  ]
}
GET/api/v2/devices/app-update
Check for a device app update

Whether a newer app version is rolled out to one of the caller's own devices, resolved against the active rollouts exactly as the device itself checks. should_update is true when a targeted version newer than the device's is available; is_mandatory is true when the device's current version is below the highest mandatory rollout (it must update before it can keep working). Pass a package to check a specific app, otherwise the platform app is used.

Query Parameters

ParameterTypeRequiredDescriptionExample
remote_device_idstringYesThe device to check. Must be owned by the caller.a1b2c3d4-e5f6-7890-abcd-ef1234567890
packagestringNoThe app package to resolve the rollout for. Defaults to the platform app package.com.xgodo.app

Responses

Status CodeDescriptionResponse Body
200Whether a newer app version is rolled out to this device (should_update) and whether it is mandatory (is_mandatory), with the target version.-
400remote_device_id (or package) missing-
401Unauthorized or missing token-
404Device not found or not owned by the caller-
500Server error-

Example Response

{
  "success": true,
  "should_update": true,
  "is_mandatory": false,
  "version_name": "3.4.1",
  "version_code": 341,
  "current_version": "3.3.0",
  "current_version_code": 330
}
GET/api/v2/devices/earnings
Get device earnings

Returns the caller's earnings for one owned device — the payout from confirmed tasks over the last 7 and 30 days — plus the device's pending, processing and confirmed task counts. Only tasks performed by the caller (as worker) on this device are counted; earnings are windowed by when each task was performed.

Query Parameters

ParameterTypeRequiredDescriptionExample
remote_device_idstringYesThe device whose earnings and task counts to return. Must be owned by the caller.a1b2c3d4-e5f6-7890-abcd-ef1234567890

Responses

Status CodeDescriptionResponse Body
200Earnings from confirmed tasks over the last 7 and 30 days (net of the referral cut), plus pending/processing/confirmed task counts for the device.-
400remote_device_id is required-
401Unauthorized or missing token-
404Device not found (or not owned by the caller)-
500Server error-

Example Response

{
  "success": true,
  "remote_device_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Atomic Mammal",
  "earnings": {
    "last_7_days": 4.25,
    "last_30_days": 18.9
  },
  "tasks": {
    "pending": 2,
    "processing": 1,
    "confirmed": 137
  }
}
POST/api/v2/devices/delete
Delete (disassociate) a device

Removes one of the caller's devices from their account: deletes the device record, declines its running tasks, and cascades cleanup of automations, airplane-trigger links and proxy connections; RMS is told to revoke the device token and drop its socket. The device must be paired again with a fresh PIN to be used afterwards.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
remote_device_idstringYesThe device to disassociate (delete) from the caller's account.a1b2c3d4-e5f6-7890-abcd-ef1234567890

Responses

Status CodeDescriptionResponse Body
200Device removed from the caller's account.-
400remote_device_id is required-
401Unauthorized or missing token-
404Device not found or not owned by the caller-
500Server error-

Example Response

{
  "success": true,
  "message": "Device removed from your account"
}
POST/api/v2/devices/market/publish
Publish a device to the PPA market

Lists one or more of the caller's devices on the per-action (PPA) market. The device must be idle, not already listed/rented, have the voice-assistant role (or an active ADB connection), and have a verified device phone number. No rental pricing applies to PPA. The device stays wanted on PPA until you unpublish it: if it loses one of these capabilities, or its battery drops to the owner's battery-rule threshold, the system takes it off the market and relists it automatically once it is eligible again. A missing capability must be back within 30 minutes, otherwise the device stays unlisted; a low battery has no time limit.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
remote_device_idstringNoThe device to publish. Provide this or device_Ids.a1b2c3d4-e5f6-7890-abcd-ef1234567890
device_Idsstring[]NoOne or more devices to publish. Provide this or remote_device_id.["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]
uptime_guaranteenumberNoUptime guarantee percentage (80–99). Defaults to 90.90

Responses

Status CodeDescriptionResponse Body
200Devices were published to the PPA market.-
400No eligible device (must be connected as voice assistant or ADB and not already listed), or missing a verified device phone number.-
401Unauthorized or missing token-

Example Response

{
  "success": true,
  "published": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]
}
POST/api/v2/devices/market/unpublish
Unpublish a device from the market

Removes one or more of the caller's devices from the market (and cancels any active live rental). Also cancels the automatic relist of a PPA device the system has temporarily taken off the market.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
device_Idsstring[]YesOne or more devices to remove from the market.["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]

Responses

Status CodeDescriptionResponse Body
200Devices were removed from the market.-
400No matching listed/rented device found.-
401Unauthorized or missing token-

Example Response

{
  "success": true
}
POST/api/v2/devices/password
Set the device screen-lock password

Saves (or clears) the screen-lock password for one of the caller's devices. The automatic-unlock automation uses it to enter the lock screen so passkey/credential tasks can run; it is stored encrypted and never returned. Send an empty string or null to clear it. Advisory only — it never affects whether the device can connect or be published. Saving a new value resets any previous automatic-unlock check result.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
remote_device_idstringYesThe caller's device to set the screen-lock password on.a1b2c3d4-e5f6-7890-abcd-ef1234567890
passwordstring | nullNoThe device's screen-lock password (max 64 characters). Stored encrypted and used only by the automatic-unlock automation. Send an empty string or null to clear it. Saving a new value clears any previous automatic-unlock check result.hunter2

Responses

Status CodeDescriptionResponse Body
200Password saved (or cleared). `password_set` tells you which.-
400Missing remote_device_id, non-string password, or password over 64 characters.-
401Unauthorized or missing token-
404No matching device owned by the caller.-

Example Response

{
  "success": true,
  "password_set": true
}
POST/api/v2/admin/devices/logs
Read a device's local logs (admin)

Reads log rows stored on the device (app/service logs and automation console logs) for a time window, via the device itself, so the device must be online and responsive. Devices keep roughly the last 24 hours of logs. Requires the admin API token.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
remote_device_idstringYesDevice to read logs from. It must be online.add02036-6754-4cf7-8432-cc28be4468f0
fromnumberNoStart of the window, epoch milliseconds (inclusive). Defaults to `to` minus 1 hour.1790150400000
tonumberNoEnd of the window, epoch milliseconds (inclusive). Defaults to now.1790154000000
searchstringNoCase-insensitive substring match on `event` or `data`.did not acknowledge
eventstringNoExact `event` match, e.g. `Automation-<automationId>-<commitId>` for one automation's console logs.-
orderstringNo`desc` (newest first, default) or `asc`.-
limitnumberNoRows per page, 1–1000 (default 500).-
offsetnumberNoRows to skip (default 0). Page with `offset += limit` while `has_more` is true.-
timeoutnumberNoSeconds to wait for the device to answer, 5–120 (default 60).-

Responses

Status CodeDescriptionResponse Body
200Log rows for the window. An empty `logs` means nothing matched or the device did not answer within `timeout`.-
400Invalid body.-
401Missing or invalid admin API token.-
404Device not found.-
409Device is offline.-
502RMS rejected or failed the request.-

Example Response

{
  "success": true,
  "remote_device_id": "add02036-6754-4cf7-8432-cc28be4468f0",
  "from": 1790150400000,
  "to": 1790154000000,
  "order": "desc",
  "limit": 500,
  "offset": 0,
  "count": 2,
  "has_more": false,
  "logs": [
    {
      "id": "1879442",
      "event": "Automation-6650f1c2a9b3e4d5f6a7b8c9-3f2a1b0",
      "data": "{\"message\":\"Opening app\",\"messageLevel\":\"LOG\"}",
      "timestamp": 1790153990000
    },
    {
      "id": "1879441",
      "event": null,
      "data": "recv 'action' name=automation",
      "timestamp": 1790153985000
    }
  ]
}

Proxy API

Manage SOCKS5/HTTP proxy credentials. A connection is routed through one of your devices, and authenticates one of two ways (chosen at creation via `auth_mode`): username/password (`connectionId` + password), or IP whitelist — no credentials, only your whitelisted source IPs may connect (HTTP needs no auth; SOCKS5 accepts any/placeholder username). The mode is stored on the connection (`authMode`) and never changes. Both modes can carry whitelisted IPs: on a username/password connection they are allowed IPs — they connect without credentials and, once any IP is whitelisted, the password is only accepted from them (no IPs ⇒ the password works from anywhere). An IP-whitelist connection must always keep at least one IP; a password connection may have zero. 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. To get a new IP on the same device instead, use the IP rotation links below. Endpoints are scoped to devices the caller owns; the admin endpoint requires the admin token.

POST/api/v2/devices/connections/list
List my connections

All connections owned by, or currently routed through, devices the caller owns. The `password` is plaintext — RMS owns the credential.

Responses

Status CodeDescriptionResponse Body
200All connections owned by, or routed through, devices the caller owns.-
401Missing or invalid API token.-

Example Response

{
  "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",
      "authMode": "ip_whitelist",
      "whitelistCount": 2
    }
  ]
}
POST/api/v2/devices/connections/create
Create a connection

Generates a fresh `connectionId` (4 letters + 8 digits) and 12-char password and associates it with the chosen device. Default `auth_mode` is `password` (save the plaintext password from the response). Pass `auth_mode: ip_whitelist` with `whitelist_ips` for a passwordless, IP-authenticated connection (seed IPs are added atomically). A `password` connection may also take optional `whitelist_ips` (allowed IPs, addable later). The mode is stored and returned as `authMode`.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
device_idstring (UUID — `remote_device_id`)YesOwned device id.-
namestringNoOptional human label (≤ 100 chars). Cosmetic only — the credential is the auto-generated `connectionId`.-
auth_mode"password" | "ip_whitelist"NoHow clients authenticate (default `password`) — STORED on the connection and fixed after creation. `password` → `connectionId` + password, optionally restricted to whitelisted IPs. `ip_whitelist` → no username/password; only whitelisted source IPs may use it (the password is hidden). Requires `whitelist_ips`.-
whitelist_ipsstring[]NoIPs/CIDRs to seed the whitelist (e.g. ["203.0.113.7", "198.51.100.0/24"]). Required (≥1) when `auth_mode` is `ip_whitelist`; optional for `password`, where whitelisted IPs connect without credentials and — once any IP is whitelisted — the password is only accepted from those IPs (none ⇒ the password works from any IP). Created atomically with the connection — if any pattern is invalid the whole create is rolled back.-

Responses

Status CodeDescriptionResponse Body
200Created — RMS generates a `connectionId` (4 letters + 8 digits) + 12-char password and pushes to the orchestrator. `authMode` is the STORED mode you chose and `whitelistCount` counts any seeded `whitelist_ips`. For a `password` connection, save the plaintext password from the response (the orchestrator stores only a hash); for an `ip_whitelist` connection the password is unused.-
400`device_id` missing, or `auth_mode` is `ip_whitelist` with no valid `whitelist_ips`, or a seed pattern is malformed (rolled back).-
404Device not owned by caller.-

Example Response

{
  "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",
    "authMode": "password",
    "whitelistCount": 0
  }
}
POST/api/v2/devices/connections/delete
Delete a connection

Cascades on RMS, orchestrator, and the legacy mirror. Active sessions are terminated.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
connection_idstringYesThe connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username.-

Responses

Status CodeDescriptionResponse Body
200Deleted on RMS, the orchestrator, and the legacy mirror. Active proxy sessions using this credential are terminated.-
403Connection not owned/routed through any device the caller owns.-
404Connection not found.-
POST/api/v2/devices/connections/password/rotate
Rotate password

Replaces the connection's password (same `connectionId`). Active sessions continue on the old credential until they close.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
connection_idstringYesThe connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username.-

Responses

Status CodeDescriptionResponse Body
200Generates a new 12-char password (same `connectionId`).-
403Forbidden.-

Example Response

{ "success": true, "password": "qK4LmPzWxR7a" }
POST/api/v2/devices/connections/association/set
Move connection to another device (IP rotation)

Hot-swap the exit device. Owner must own both the source connection and the target device.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
connection_idstringYesThe connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username.-
device_idstring (UUID)YesTarget device — **must also be owned by the caller**.-

Responses

Status CodeDescriptionResponse Body
200Hot-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.-
403Source connection or target device not owned by caller.-
POST/api/v2/devices/connections/association/delete
Remove association

Connection stays alive but cannot authenticate until re-associated.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
connection_idstringYesThe connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username.-

Responses

Status CodeDescriptionResponse Body
200Connection stays but cannot route until a new association is set. Active sessions are terminated.-
403Forbidden.-
POST/api/v2/devices/connections/blacklist/list
List per-connection blacklist

Per-connection block list (in addition to global + per-device).

Request Body Parameters

ParameterTypeRequiredDescriptionExample
connection_idstringYesThe connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username.-

Responses

Status CodeDescriptionResponse Body
200Per-connection blacklist domains (in addition to the global blacklist). Suffix matching applies.-
403Forbidden.-

Example Response

{ "success": true, "domains": ["example.com", "tracker.io"] }
POST/api/v2/devices/connections/blacklist/add
Add 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 Parameters

ParameterTypeRequiredDescriptionExample
connection_idstringYesThe connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username.-
domainsstring[]YesNon-empty array of domains to add or remove.-

Responses

Status CodeDescriptionResponse Body
200Updated.-
403Forbidden.-
POST/api/v2/devices/connections/blacklist/remove
Remove domains from blacklist

Removes one or more domains from the connection's blacklist.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
connection_idstringYesThe connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username.-
domainsstring[]YesNon-empty array of domains to add or remove.-

Responses

Status CodeDescriptionResponse Body
200Updated.-
403Forbidden.-
POST/api/v2/devices/connections/whitelist/list
List 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 Parameters

ParameterTypeRequiredDescriptionExample
connection_idstringYesThe connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username.-

Responses

Status CodeDescriptionResponse Body
200Array of whitelisted IP patterns (either mode). On an `ip_whitelist` connection these are the only allowed sources. On a `password` connection they are the allowed IPs: they connect without credentials and, when non-empty, the password is only accepted from them. The list does not determine the mode — read `authMode` from the connection.-
401Missing or invalid API token.-

Example Response

{
  "success": true,
  "ips": ["203.0.113.5", "10.0.0.0/8"]
}
POST/api/v2/devices/connections/whitelist/add
Add IP to whitelist

Adds an IP or CIDR to the connection's whitelist — either mode. On a username/password connection the first whitelisted IP starts restricting password logins to the whitelisted IPs (whitelisted IPs themselves connect without credentials). Idempotent — re-posting the same pattern updates its label. Domain patterns are rejected (use blacklist for domain-level control).

Request Body Parameters

ParameterTypeRequiredDescriptionExample
connection_idstringYesThe connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username.-
patternstringYesIP address or CIDR to whitelist (e.g. `1.2.3.4` or `10.0.0.0/8`). Domain patterns are rejected.-
labelstringNoOptional human label (e.g. `office`, `CI runner`).-

Responses

Status CodeDescriptionResponse Body
200Whitelist entry added (idempotent — re-adding a pattern updates its label).-
400Invalid pattern (must be IP or CIDR).-
401Missing or invalid API token.-
502Gost orchestrator unreachable.-
POST/api/v2/devices/connections/whitelist/remove
Remove IP from whitelist

Removes an IP/CIDR from the connection's whitelist. On an IP-whitelist connection removing the LAST IP is refused (409) — it must always keep at least one; delete the connection instead. A username/password connection may remove all its IPs (the password then works from any IP).

Request Body Parameters

ParameterTypeRequiredDescriptionExample
connection_idstringYesThe connection's id — a 12-char token (4 letters + 8 digits), also the SOCKS5 username.-
patternstringYesIP or CIDR to remove from the whitelist.-

Responses

Status CodeDescriptionResponse Body
200Whitelist entry removed.-
401Missing or invalid API token.-
409The pattern is the LAST whitelisted IP of an `ip_whitelist` connection, which must keep at least one — delete the connection instead. A `password` connection may remove all its IPs (the password then works from any IP).-
502Gost orchestrator unreachable.-
POST/api/v2/devices/transport-mode/get
Get device transport mode

Returns the current egress transport mode for the device. Determines which network interface outbound traffic exits through.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
device_idstring (UUID — `remote_device_id`)YesOwned device id.-

Responses

Status CodeDescriptionResponse Body
200Mode returned or updated.-
400Invalid mode value.-
401Missing or invalid API token.-
403Device not owned by caller.-
502Gost orchestrator unreachable.-
POST/api/v2/devices/transport-mode/set
Set 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 Parameters

ParameterTypeRequiredDescriptionExample
device_idstring (UUID — `remote_device_id`)YesOwned device id.-
modestringYes`cellular` (default — carrier IP), `wifi` (WiFi router's IP), or `auto` (prefer cellular, fallback to WiFi).-

Responses

Status CodeDescriptionResponse Body
200Mode returned or updated.-
400Invalid mode value.-
401Missing or invalid API token.-
403Device not owned by caller.-
502Gost orchestrator unreachable.-

IP rotation links

Besides moving a connection to another device, you can rotate the IP of the device itself. An IP rotation link is a public URL that toggles airplane mode on the device, so the carrier hands it a new IP. Anyone holding the URL can trigger it (no API token), so hand out one link per client and rotate it if it leaks. Triggers are rate limited by the device's minimum-interval setting (device settings page): a request inside that window returns 429 with the seconds to wait, and a request the device cannot act on (offline, toggle failed) returns 500. Proxy sessions through the device drop while the radio cycles; new sessions exit with the new IP once the device reconnects. The device settings page lists a device's links and its trigger log.

# No API token needed — GET or POST the link URL
curl https://xgodo.com/ip-rotate/665f1c2ab7e4d3a9c8f01234

{ "success": true, "message": "Airplane mode triggered successfully" }

# Too soon after the previous rotation (device min-interval setting)
{ "success": false, "message": "Please wait 42s before triggering again" }   # HTTP 429
POST/api/v2/devices/proxy-logs/list
Query 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 Parameters

ParameterTypeRequiredDescriptionExample
fromstring (ISO 8601)YesInclusive lower bound on `timestamp`.-
tostring (ISO 8601)YesInclusive upper bound on `timestamp`.-
connectionIdstringNoFilter to one connection.-
deviceIdstring (UUID)NoFilter to one device — must be owned by the caller.-
targetstringNoSubstring match on the request target (`host:port`).-
action"allowed" | "blocked"NoFilter by outcome.-
limitintegerNoPage size (default 100, max 10000).-
offsetintegerNo0-indexed offset for pagination.-

Responses

Status CodeDescriptionResponse Body
200Request log rows from ClickHouse. Post-filtered to devices owned by the caller.-
400`from`/`to` missing or invalid; `limit` out of range.-

Example Response

{
  "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
    }
  ]
}
POST/api/v2/devices/proxy-bandwidth
Query 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 Parameters

ParameterTypeRequiredDescriptionExample
deviceIdstring (UUID)NoFilter to one device — must be owned by the caller. Omit to fetch per-device rows for every device the caller owns.-
connectionIdstringNoFilter to one connection.-
fromstring (ISO 8601)NoInclusive lower bound.-
tostring (ISO 8601)NoInclusive upper bound.-
groupBy"hour" | "day"NoTime-bucket granularity.-

Responses

Status CodeDescriptionResponse Body
200Aggregated bandwidth rows from the orchestrator.-

Example Response

{
  "success": true,
  "rows": [
    {
      "deviceId": "add02036-6754-4cf7-8432-cc28be4468f0",
      "connectionId": "wXyZ04827193",
      "bytesIn": 152841234,
      "bytesOut": 9183748,
      "bucket": "2026-04-25T13:00:00Z"
    }
  ]
}
GET/api/v2/admin/devices/proxies
List 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.

Request Body Parameters

ParameterTypeRequiredDescriptionExample

Responses

Status CodeDescriptionResponse Body
200Connection list with associated device + bandwidth.-
401Missing or invalid admin API token.-
502Could not reach the proxy orchestrator.-
503Proxy orchestrator / RMS not configured (env vars missing).-

Example Response

{
  "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 API

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.)

GET/api/v2/bucket/:job_id
List 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

ParameterTypeRequiredDescriptionExample
job_idstring (ObjectId)YesPath 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

ParameterTypeRequiredDescriptionExample
pageintegerNo1-indexed page. Defaults to `1`.1
limitintegerNoPage size. Defaults to `20`. Capped at `100`.20

Responses

Status CodeDescriptionResponse Body
200Paginated list of buckets for this job (newest first by `updated_at`). Each entry is enriched with the device's display name.-
404Job 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" }
500Unexpected DB error.{ "error": "Internal server error" }

Example Response

{
  "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
}
DELETE/api/v2/bucket/:job_id
Delete all buckets for a job

Wipes every bucket associated with this job. Caller must have `edit` access (owner or shared-edit).

Path Parameters

ParameterTypeRequiredDescriptionExample
job_idstring (ObjectId)YesPath 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 CodeDescriptionResponse Body
200All buckets for this job removed. Returns the count deleted.{ "success": true, "deleted": 12 }
404Job 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" }
500Unexpected DB error.{ "error": "Internal server error" }
GET/api/v2/bucket/:job_id/:device_id
Read a single bucket

Returns the bucket for this `(job_id, device_id)` pair. 404 when none exists or the job is inaccessible.

Path Parameters

ParameterTypeRequiredDescriptionExample
job_idstring (ObjectId)YesPath 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_idstring (UUID)YesPath param. Device's `remote_device_id`.-

Responses

Status CodeDescriptionResponse Body
200Returns the bucket document for this `(job_id, device_id)` pair.-
404Either the job is inaccessible to the caller, or no bucket exists for this device.{ "error": "Bucket not found" }
500Unexpected DB error.{ "error": "Internal server error" }

Example Response

{
  "bucket": {
    "_id": "65f1...",
    "job_id": "65aa...",
    "remote_device_id": "8b2f-uuid",
    "data": { "sessionToken": "abc123", "loggedIn": true },
    "updated_at": "2026-04-15T08:14:00.000Z"
  }
}
PUT/api/v2/bucket/:job_id/:device_id
Write 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

ParameterTypeRequiredDescriptionExample
job_idstring (ObjectId)YesPath 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_idstring (UUID)YesPath param. Device's `remote_device_id`.-

Request Body Parameters

ParameterTypeRequiredDescriptionExample
dataobjectYesNew bucket payload. Replaces the existing object (no merge). If the linked automation defines a `bucket_schema`, `data` is validated against it.-

Responses

Status CodeDescriptionResponse Body
200Bucket 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" }
404Job 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" }
500Unexpected DB error.{ "error": "Internal server error" }
DELETE/api/v2/bucket/:job_id/:device_id
Delete a bucket

Removes the bucket for this `(job_id, device_id)` pair. Caller must have `edit` access.

Path Parameters

ParameterTypeRequiredDescriptionExample
job_idstring (ObjectId)YesPath 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_idstring (UUID)YesPath param. Device's `remote_device_id`.-

Responses

Status CodeDescriptionResponse Body
200Bucket for this `(job_id, device_id)` pair removed.{ "success": true }
404Job 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" }
500Unexpected DB error.{ "error": "Internal server error" }

Device Bucket API

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`.

POST/api/v2/device-bucket
Read 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 Parameters

ParameterTypeRequiredDescriptionExample
remote_device_idstring (UUID)NoThe 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 CodeDescriptionResponse Body
200Returns the device-scoped bucket. `deviceBucket` is `{}` on first read.-
400Token has no device context and `remote_device_id` was not supplied.{ "error": "Device bucket requires a device context (remote_device_id)" }

Example Response

{
  "deviceBucket": {
    "accountToken": "xyz",
    "username": "user@example.com"
  }
}
POST/api/v2/device-bucket/set
Merge into device bucket

Merges the supplied object into the existing device bucket. Existing keys not in the payload are preserved.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
remote_device_idstring (UUID)NoThe 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.-
dataobjectYesNon-null, non-array JSON object. Top-level keys are merged with the stored bucket — existing keys not in the payload are preserved.-

Responses

Status CodeDescriptionResponse Body
200Bucket merged and persisted. Returns the full merged object.-
400Missing device context or `data` is missing / null / an array / not an object.{ "error": "Invalid data. Must be a non-null object." }

Example Response

{
  "success": true,
  "deviceBucket": {
    "accountToken": "xyz",
    "username": "user@example.com"
  }
}

Recordings API

Screen recordings of automation tasks. A device-automation job can enable Screen Recording in its options (quality, max duration, sampling, per-device / job limits, retention), and a planned task can force or disable recording with its screen_recording field (see Submit planned tasks). For every task launch the server creates a recording record and asks the device to record its screen for the lifetime of the automation; the device records only when screen capture is already permitted (in-app ADB or a held screen-capture permission, never a consent dialog), uploads the MP4 after the task ends, and the server deletes the video after the job's retention period. Every record carries a status (requested, skipped, recording, uploading, uploaded, failed, expired, deleted) and, when it was not recorded, a status_reason stating exactly why. These endpoints cover the caller's own jobs and jobs shared with them (view or edit): shared users can look up, list and play recordings; only the job owner can delete one.

GET/api/v2/recordings/task/:job_task_id
Get the recording of a task

Returns the recording record attached to a task, or null when the task was launched without recording. Poll this while status is requested, recording or uploading; received_bytes and size_bytes give upload progress. While uploading, upload_completed_at is null until the device has sent every byte; once set, the server is saving the video to storage and status turns uploaded when that finishes. The response never includes the device upload token or the storage key.

Path Parameters

ParameterTypeRequiredDescriptionExample
job_task_idstring (ObjectId)YesThe task's job_task_id. The caller must own the task's job or have it shared with them.60c72b2f9b1e8c001c8e4d8a

Responses

Status CodeDescriptionResponse Body
200The recording record, or null when none exists for this task.{"success": true, "recording": { ... }}
400Invalid job_task_id.{"success": false, "message": "Invalid job_task_id"}
401Missing or invalid Client API token.-
404Recording (or its job / task) not found, or the caller has no access to the job (not the owner and not shared with them; for delete, not the owner).{"success": false, "message": "Recording not found"}

Example Response

{
  "success": true,
  "recording": {
    "recording_id": "3f2a6c1e-8b4d-4e5f-9a0b-1c2d3e4f5a6b",
    "job_task_id": "60c72b2f9b1e8c001c8e4d8a",
    "job_id": "60c72b2f9b1e8c001c8e4d00",
    "remote_device_id": "9b1e8c00-1c8e-4d8a-b2f9-60c72b2f9b1e",
    "automation_id": "60c72b2f9b1e8c001c8e4d11",
    "status": "uploaded",
    "status_reason": null,
    "quality": "medium",
    "max_duration_minutes": 0,
    "started_at": "2026-10-16T09:00:05.000Z",
    "ended_at": "2026-10-16T09:03:09.000Z",
    "duration_ms": 184000,
    "stop_reason": "automation_stopped",
    "width": 720,
    "height": 1600,
    "mime": "video/mp4",
    "size_bytes": 12582912,
    "received_bytes": 12582912,
    "upload_completed_at": "2026-10-16T09:03:12.000Z",
    "uploaded_at": "2026-10-16T09:03:20.000Z",
    "retention_days": 7,
    "expires_at": "2026-10-23T09:03:20.000Z",
    "created_at": "2026-10-16T09:00:00.000Z",
    "updated_at": "2026-10-16T09:03:20.000Z"
  }
}
GET/api/v2/recordings/job/:job_id
List a job's recordings

Paginated list of every recording record of a job, newest first, with the device's display name. Use the status filter to find, for example, only uploaded recordings or every skipped one with its reason.

Path Parameters

ParameterTypeRequiredDescriptionExample
job_idstring (ObjectId)YesThe job's job_id. The caller must own the job or have it shared with them.60c72b2f9b1e8c001c8e4d8a

Query Parameters

ParameterTypeRequiredDescriptionExample
pageintegerNo1-indexed page. Defaults to 1.1
limitintegerNoPage size, 1-100. Defaults to 20.20
statusstringNoFilter by status: requested, skipped, recording, uploading, uploaded, failed, expired or deleted.uploaded

Responses

Status CodeDescriptionResponse Body
200Paginated recordings.{"success": true, "recordings": [ ... ], "total": 42, "page": 1, "limit": 20}
400Invalid job_id or query parameters.{"success": false, "message": "Invalid job_id"}
401Missing or invalid Client API token.-
404Job not found, or neither owned by nor shared with the caller.{"success": false, "message": "Job not found"}

Example Response

{
  "success": true,
  "recordings": [
    {
      "recording_id": "3f2a6c1e-8b4d-4e5f-9a0b-1c2d3e4f5a6b",
      "job_task_id": "60c72b2f9b1e8c001c8e4d8a",
      "status": "uploaded",
      "status_reason": null,
      "quality": "medium",
      "duration_ms": 184000,
      "size_bytes": 12582912,
      "uploaded_at": "2026-10-16T09:03:20.000Z",
      "expires_at": "2026-10-23T09:03:20.000Z",
      "device_name": "Atomic Mammal",
      "...": "same fields as the single-recording response"
    }
  ],
  "total": 42,
  "page": 1,
  "limit": 20
}
GET/api/v2/recordings/:recording_id/url
Get a playback URL

Returns a time-limited playback URL for an uploaded recording, valid for one hour, suitable for a video src or a direct download. The URL is self-contained (the signature is the credential): it needs no Authorization header, supports HTTP Range requests (206 Partial Content, Accept-Ranges: bytes) and serves video/mp4. Request a fresh URL when it expires. Only recordings in uploaded status have a URL.

Path Parameters

ParameterTypeRequiredDescriptionExample
recording_idstring (UUID)YesThe recording's recording_id (minted by the server when the task was launched). The caller must own the job the recording belongs to or have it shared with them; deleting needs the owner.3f2a6c1e-8b4d-4e5f-9a0b-1c2d3e4f5a6b

Responses

Status CodeDescriptionResponse Body
200Playback URL and its expiry.{"success": true, "url": "https://...", "expires_at": "2026-10-16T10:03:20.000Z"}
400The recording is not in uploaded status (the body carries its current status).{"success": false, "message": "Recording is not available for playback", "status": "skipped"}
401Missing or invalid Client API token.-
404Recording (or its job / task) not found, or the caller has no access to the job (not the owner and not shared with them; for delete, not the owner).{"success": false, "message": "Recording not found"}

Example Response

{
  "success": true,
  "url": "https://xgodo.com/recordings/2026/10/3f2a6c1e-8b4d-4e5f-9a0b-1c2d3e4f5a6b.mp4?md5=...&expires=1760605400",
  "expires_at": "2026-10-16T10:03:20.000Z"
}
DELETE/api/v2/recordings/:recording_id
Delete a recording

Removes the video from storage and marks the record deleted. The record itself is kept, so the task still shows that a recording existed; a deleted recording cannot be restored. Deleting an already deleted recording succeeds. Owner only: users the job is shared with get 404.

Path Parameters

ParameterTypeRequiredDescriptionExample
recording_idstring (UUID)YesThe recording's recording_id (minted by the server when the task was launched). The caller must own the job the recording belongs to or have it shared with them; deleting needs the owner.3f2a6c1e-8b4d-4e5f-9a0b-1c2d3e4f5a6b

Responses

Status CodeDescriptionResponse Body
200Video removed.{"success": true}
401Missing or invalid Client API token.-
404Recording (or its job / task) not found, or the caller has no access to the job (not the owner and not shared with them; for delete, not the owner).{"success": false, "message": "Recording not found"}

Automation Git API

Read-only access to an automation project's git history. Each automation project is backed by an isomorphic-git repository on the server; every commit from the web editor or the CLI shows up here. Accessible to the automation's owner, editors and contributors with a Bearer token.

GET/api/v2/automation-project/:id/git/history-paginated
View paginated commit history

Walks the commit log and returns one page at a time, newest-first. Use `page` + `limit` to scroll; `hasMore` is `true` while there's a next page. Server fetches up to `(page × limit) + 1` commits per call — deep pagination gets slower, which is fine for automation repos that stay small.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-

Query Parameters

ParameterTypeRequiredDescriptionExample
limitintegerNoPage size. Default `20`, max `100`. Values below `1` fall back to the default.20
pageintegerNo1-indexed page number. Default `1`.1

Responses

Status CodeDescriptionResponse Body
200Page of commits plus `page`, `limit`, `hasMore`. Each commit has `hash`, `message`, `author`, `authorEmail`, `parents`, `timestamp` (ISO 8601).-
400Validation error (bad `page` or `limit`).-
404Automation not found, is a legacy automation (no git repo), or the caller is not the owner, an editor or a contributor.{ "success": false, "message": "Automation not found or is a legacy automation" }

Example Response

{
  "success": true,
  "page": 1,
  "limit": 20,
  "hasMore": true,
  "commits": [
    {
      "hash": "5c1e9a9f...",
      "message": "Handle new consent dialog\n",
      "author": "jane",
      "authorEmail": "jane@example.com",
      "parents": ["a93d41e2..."],
      "timestamp": "2026-09-28T11:30:00.000Z"
    }
  ]
}
GET/api/v2/automation-project/:id/git/commit/:hash
View git log for a commit

Returns the git log entry for one commit — the same fields `git show` would print as a header (message, author, timestamp, parent and tree oids). Use this to resolve a hash from the paginated history into a full record, or to check a commit's parent chain. The hash can be a merge commit or a regular commit.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
hashstringYesPath param. Full commit hash (git oid) to view.-

Responses

Status CodeDescriptionResponse Body
200Commit details: `hash`, `message`, `author`, `timestamp` (ISO 8601), `tree`, `parent` (array of parent commit oids; empty for the initial commit, two entries for a merge).-
404Automation not found, is a legacy automation (no git repo), or the caller is not the owner, an editor or a contributor.{ "success": false, "message": "Automation not found or is a legacy automation" }
404Caller is a contributor and the commit isn't part of main's history (e.g. an unmerged pull request commit).{ "success": false, "message": "Commit not found" }
500Hash doesn't exist in the repository, or the oid points to a non-commit object.-

Example Response

{
  "success": true,
  "commit": {
    "hash": "5c1e9a9f...",
    "message": "Handle new consent dialog\n",
    "author": "jane",
    "timestamp": "2026-09-28T11:30:00.000Z",
    "tree": "41aa...",
    "parent": ["a93d41e2..."]
  }
}

Automation Pull Requests API

Propose changes to an automation without commit access, and review them. Users shared as contributors on an automation can read its latest committed code and open pull requests; each pull request gets a private workspace (a copy of the code) that only its author can edit and commit to. The owner and editors review, comment, merge or close. If main has moved on since a pull request was submitted (e.g. another one was merged first), it can still be merged directly when the changes don't conflict — the server three-way merges it onto the latest main and regenerates compiled .js files from the merged .ts. Only a conflicting pull request needs the author to call "update from main" (conflicts are left as markers to resolve and commit). Merging creates a commit on main that devices run immediately. All endpoints use Bearer-token auth; the same operations exist in the web editor.

GET/api/v2/automation-project/:id/head/files
List files at the latest commit

Read-only file tree of the latest commit on main (uncommitted work in the owner's editor is not visible). Owner, editors and contributors.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-

Responses

Status CodeDescriptionResponse Body
200Returns `{ commit, files }` where `files` is a recursive tree of `{ name, path, type, children? }`.-
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
GET/api/v2/automation-project/:id/head/file
Read a file at the latest commit

Returns `{ content, path, commit }`. Owner, editors and contributors.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-

Query Parameters

ParameterTypeRequiredDescriptionExample
pathstringYesProject-relative file path.src/main.ts

Responses

Status CodeDescriptionResponse Body
200File content (UTF-8).-
404No access, or the file doesn't exist at the latest commit.-
GET/api/v2/automation-project/:id/prs
List pull requests

Owner and editors see every pull request; contributors see their own. Newest activity first, max 200.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-

Query Parameters

ParameterTypeRequiredDescriptionExample
statusstringNo`"active"` (draft + open, default), `"draft"`, `"open"`, `"merged"`, `"closed"` or `"all"`.-

Responses

Status CodeDescriptionResponse Body
200List of pull requests plus the caller's `role` (`owner` | `editor` | `contributor`).-
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }

Example Response

{
  "success": true,
  "role": "contributor",
  "pull_requests": [
    {
      "number": 3,
      "title": "Fix login step on Android 14",
      "status": "open",
      "author_id": "676d...",
      "author_username": "jane",
      "comment_count": 2,
      "has_conflicts": false,
      "created_at": "2026-09-28T10:00:00.000Z",
      "updated_at": "2026-09-28T11:30:00.000Z",
      "merged_at": null,
      "closed_at": null
    }
  ]
}
POST/api/v2/automation-project/:id/prs
Open a pull request

Contributors only. Creates the pull request and its workspace from the latest commit. A contributor can have at most 20 draft/open pull requests per automation.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-

Request Body Parameters

ParameterTypeRequiredDescriptionExample
titlestringYes1–200 chars.Fix login step on Android 14
descriptionstringNo≤ 10000 chars.-
draftbooleanNoDefault `true`. Drafts can't be merged and reviewers aren't notified until the PR is moved to `"open"`.-

Responses

Status CodeDescriptionResponse Body
201Created.-
400Validation error, or too many open pull requests.-
403Caller is the owner or an editor (they commit directly).-
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }

Example Response

{
  "success": true,
  "pull_request": { "number": 3, "status": "draft", "title": "Fix login step on Android 14" }
}
GET/api/v2/automation-project/:id/prs/:number
Get a pull request

Details, commits (those not in main), comments, and the caller's permissions. For draft/open pull requests `merge_state` is `{ state: "ready" }`, `{ state: "no_changes" }` or `{ state: "behind", conflicts: [...] }` — main has moved on; with an empty `conflicts` it can be merged as is, otherwise `conflicts` lists the files the author must resolve by updating from main.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Responses

Status CodeDescriptionResponse Body
200Pull request details.-
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }

Example Response

{
  "success": true,
  "pull_request": {
    "number": 3,
    "title": "Fix login step on Android 14",
    "description": "...",
    "status": "open",
    "author_username": "jane",
    "base_commit": "a93d...",
    "head_commit": "5c1e...",
    "merged_commit": null,
    "conflict_files": [],
    "merge_state": { "state": "ready" },
    "main_has_uncommitted_changes": false,
    "commits": [{ "hash": "5c1e...", "message": "Handle new consent dialog", "author": "jane", "parents": 1, "timestamp": "..." }],
    "comments": [{ "id": "...", "username": "owner", "body": "Looks good", "created_at": "..." }]
  },
  "permissions": { "role": "contributor", "is_author": true, "can_edit": true, "can_merge": false, "can_close": true, "can_comment": true }
}
PATCH/api/v2/automation-project/:id/prs/:number
Edit a pull request / mark ready

Author only, while draft/open. Set `status: "open"` to request review (notifies the owner and editors) or `"draft"` to take it back.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Request Body Parameters

ParameterTypeRequiredDescriptionExample
titlestringNo1–200 chars.-
descriptionstringNo≤ 10000 chars.-
statusstringNo`"draft"` or `"open"`.-

Responses

Status CodeDescriptionResponse Body
200Returns `{ pull_request: { number, title, status } }`.-
403Not the author, or the pull request is merged/closed.-
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
GET/api/v2/automation-project/:id/prs/:number/diff
Pull request diff

Files the pull request changes, with full content: `{ fromHash, toHash, files: [{ path, status, original, modified }] }`. For merged pull requests, the change the merge commit introduced.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Responses

Status CodeDescriptionResponse Body
200Diff.-
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
GET/api/v2/automation-project/:id/prs/:number/files
Workspace: list files with hashes

Author only. Every workspace file with its git blob hash and size — compare against local files to decide what to sync (same contract as `GET /api/v2/automation-project/:id/files`).

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Responses

Status CodeDescriptionResponse Body
200Returns `{ files: [{ path, hash, size }] }`.-
400The pull request is `merged` or `closed` (its workspace no longer exists).{ "success": false, "message": "Pull request is merged" }
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
GET/api/v2/automation-project/:id/prs/:number/file
Workspace: read a file

Author only. Returns `{ content }` (UTF-8).

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Query Parameters

ParameterTypeRequiredDescriptionExample
pathstringYesProject-relative file path.src/main.ts

Responses

Status CodeDescriptionResponse Body
200File content.-
400Invalid path (outside the workspace or inside `.git`).-
404File not found, or no access.-
POST/api/v2/automation-project/:id/prs/:number/sync
Workspace: upload files

Author only. Writes files into the workspace (uncommitted until you commit). Body limit 50 MB.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Request Body Parameters

ParameterTypeRequiredDescriptionExample
files{ path: string, content: string }[]YesFiles to create or overwrite; `content` is base64. `.ts` files are compiled and the paired `.js` is written too. Paths outside the workspace or inside `.git` are skipped.-

Responses

Status CodeDescriptionResponse Body
200Written files with their new hashes; compile errors are reported per file as `errors`.-
400The pull request is `merged` or `closed` (its workspace no longer exists).{ "success": false, "message": "Pull request is merged" }
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }

Example Response

{
  "success": true,
  "updated": [
    { "path": "src/main.js", "hash": "9f1c...", "compiled": true },
    { "path": "src/main.ts", "hash": "41aa...", "compiled": true }
  ]
}
DELETE/api/v2/automation-project/:id/prs/:number/file
Workspace: delete a file

Author only. Deleting a `.ts` file also deletes its compiled `.js`; deleting a compiled `.js` directly is rejected.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Query Parameters

ParameterTypeRequiredDescriptionExample
pathstringYesProject-relative file path.src/main.ts

Responses

Status CodeDescriptionResponse Body
200Deleted.{ "success": true, "path": "src/old.ts" }
404File not found.-
400The pull request is `merged` or `closed` (its workspace no longer exists).{ "success": false, "message": "Pull request is merged" }
GET/api/v2/automation-project/:id/prs/:number/status
Workspace: uncommitted changes

Author only. Returns `{ hasChanges, changes: [{ path, status }], head_commit, update_in_progress, conflict_files }`.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Responses

Status CodeDescriptionResponse Body
200Status.-
403Only the pull request author can do this.-
400The pull request is `merged` or `closed` (its workspace no longer exists).{ "success": false, "message": "Pull request is merged" }
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
GET/api/v2/automation-project/:id/prs/:number/workspace-diff
Workspace: diff of uncommitted changes

Author only. Returns `{ commitHash, files: [{ path, status, original, modified }] }`.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Responses

Status CodeDescriptionResponse Body
200Diff.-
403Only the pull request author can do this.-
400The pull request is `merged` or `closed` (its workspace no longer exists).{ "success": false, "message": "Pull request is merged" }
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
POST/api/v2/automation-project/:id/prs/:number/commit
Commit to the pull request

Author only. Commits all workspace changes to the pull request (never to main). Completes a pending update from main. New commits on an open pull request email the automation's PR notification addresses at most every 15 minutes.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Request Body Parameters

ParameterTypeRequiredDescriptionExample
messagestringYes1–1000 chars.-

Responses

Status CodeDescriptionResponse Body
201Returns `{ commit: { hash, message } }`.-
400No changes, or files still contain conflict markers (listed in `files`).{ "success": false, "message": "Resolve the conflict markers before committing", "files": ["src/main.ts"] }
403Only the pull request author can do this.-
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
POST/api/v2/automation-project/:id/prs/:number/update
Update from main

Author only. Merges main's latest commit into the pull request. The workspace must have no uncommitted changes. A clean merge is committed automatically.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Responses

Status CodeDescriptionResponse Body
200`status` is `"up_to_date"` or `"updated"`; returns `head_commit`.-
409Conflicts. Merged files with conflict markers are left in the workspace: fix them, then commit — or discard all changes to cancel the update.{ "success": false, "status": "conflict", "conflict_files": ["src/main.ts"] }
400Uncommitted changes, or an update is already in progress.-
403Only the pull request author can do this.-
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
POST/api/v2/automation-project/:id/prs/:number/discard
Discard workspace changes

Author only. With `path`, restores that file; without, discards everything (and cancels a pending update from main).

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Request Body Parameters

ParameterTypeRequiredDescriptionExample
pathstringNoFile to discard.-

Responses

Status CodeDescriptionResponse Body
200Discarded.{ "success": true }
403Only the pull request author can do this.-
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
POST/api/v2/automation-project/:id/prs/:number/comments
Comment on a pull request

Anyone who can see the pull request, while it's draft/open. Max 500 comments per pull request.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Request Body Parameters

ParameterTypeRequiredDescriptionExample
bodystringYes1–10000 chars.-

Responses

Status CodeDescriptionResponse Body
201Returns `{ comment: { id, user_id, body, created_at } }`.-
400The pull request is `merged` or `closed` (its workspace no longer exists).{ "success": false, "message": "Pull request is merged" }
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
POST/api/v2/automation-project/:id/prs/:number/merge
Merge a pull request

Owner or editors, open pull requests only. **The merged code goes live on devices immediately.** The automation's own working tree must have no uncommitted changes.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Request Body Parameters

ParameterTypeRequiredDescriptionExample
squashbooleanNoDefault `false` (merge commit). `true` creates a single-parent commit on main.-
messagestringNoCommit message. Defaults to `Merge pull request #<n>: <title>`.-

Responses

Status CodeDescriptionResponse Body
200Returns `{ merged_commit }`.{ "success": true, "merged_commit": "2904c89..." }
400Draft, or nothing to merge.-
403Caller is a contributor.-
409Conflicts with main (`files` lists them; the author must update from main and resolve them), author is resolving conflicts, the working tree has uncommitted changes, or the pull request is no longer open.-
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
POST/api/v2/automation-project/:id/prs/:number/close
Close a pull request

Owner, editors or the author. Closes without merging and deletes the workspace; committed work stays visible in the pull request.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-
numberintegerYesPath param. Pull request number (sequential per automation).-

Responses

Status CodeDescriptionResponse Body
200Closed.{ "success": true }
400The pull request is `merged` or `closed` (its workspace no longer exists).{ "success": false, "message": "Pull request is merged" }
404Automation or pull request not found, or no access. Contributors only see their own pull requests.{ "success": false, "message": "Pull request not found" }
GET/api/v2/automation-project/:id/pr-settings
Get pull request email settings

Owner or editors. Returns `{ pr_notifications: { email_enabled, additional_emails } }`.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-

Responses

Status CodeDescriptionResponse Body
200Settings.-
404Not found, or caller isn't the owner or an editor.-
PATCH/api/v2/automation-project/:id/pr-settings
Update pull request email settings

Owner or editors. Pull request emails (opened, new commits, comments, merged, closed) go only to `additional_emails` — never to users' account emails — and skip whoever triggered the event. The owner, editors and author get in-app notifications.

Path Parameters

ParameterTypeRequiredDescriptionExample
idstring (ObjectId)YesPath param. Automation id.-

Request Body Parameters

ParameterTypeRequiredDescriptionExample
email_enabledbooleanNoTurn pull request emails on/off for this automation.-
additional_emailsstring[]NoUp to 10 addresses (lowercased, deduplicated).-

Responses

Status CodeDescriptionResponse Body
200Updated settings.-
400Invalid email or more than 10.-

Other API

File upload and temporary file access endpoints.

POST/api/v2/files/upload
Upload 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 Parameters

ParameterTypeRequiredDescriptionExample
filefileYesFile to upload (multipart/form-data)Binary file data

Responses

Status CodeDescriptionResponse Body
200File 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" } }
400No file uploaded{ "success": false, "message": "No file uploaded" }
401Unauthorized or missing tokenAuthentication error message
500Server error during upload{ "success": false, "message": "Error uploading file", "error": "Error details" }

Example Response

{
  "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"
  }
}
GET/api/v2/files/temp/:filename
Get uploaded file

Retrieves a previously uploaded temporary file. Files expire after 15 minutes from upload time.

Request Body Parameters

ParameterTypeRequiredDescriptionExample
filenamestringYesFilename returned from the upload endpoint (path parameter)1234567890_example.pdf

Responses

Status CodeDescriptionResponse Body
200File retrieved successfullyBinary file data
400Filename is required{ "success": false, "message": "Filename is required" }
404File not found or expired{ "success": false, "message": "File not found or expired" }
401Unauthorized or missing tokenAuthentication error message
500Server error during retrieval{ "success": false, "message": "Error retrieving file", "error": "Error details" }