# MetaGhost Desktop 1.0.81: API documentation

Local API for preset-driven image and video processing, job management, uploads, downloads and Google Drive transfers.

## Version and scope

Target: Desktop 1.0.81, release v1.0.81. Default base URL: http://127.0.0.1:3847/api. The server runs inside the installed Desktop on the user's computer, not on metaghost.io. Desktop must remain open. Configure Settings > API, generate a key, enable the server and Apply changes. Keep remote access disabled for local scripts.

Supported: preset-driven image/video spoofing, raw upload, local download, job management and Google Drive jobs. Other Desktop tools do not automatically have API endpoints. Use only the routes below and no operation selector. Do not invent endpoints, request fields, cost estimates or credit-balance queries.

## Connection and credentials

Use the port displayed in Settings > API. If it differs from 3847, replace the port in every request and example. Server settings are configured in Desktop; there is no API route to change them. Processing settings are saved in Desktop presets and selected by their exact name.

GET /api/health requires no credentials and reports server availability, Desktop version and active jobs. Example: {"status":"ok","version":"1.0.81","activeJobs":0}; activeJobs varies. Check compatibility when the installed version differs from this reference. All other routes require Authorization: Bearer YOUR_LOCAL_API_KEY.

Read the key from an environment variable such as METAGHOST_API_KEY or your integration platform’s secret store. Send it in the Authorization header. Never embed it in code, URLs, exported configurations, logs or an AI conversation. Do not follow redirects with credentials. The standard local starters use loopback only.

## Available routes

- `GET /api/health`: Check Desktop 1.0.81 and active jobs
- `GET /api/presets`: List saved image and video preset names
- `GET /api/drive/status`: Check the Desktop Google Drive connection
- `POST /api/upload`: Upload raw image or video bytes
- `POST /api/jobs`: Create an image/video spoofing job
- `GET /api/jobs`: List the 50 most recent jobs and queue state
- `DELETE /api/jobs`: Clear finished jobs from in-memory history
- `GET /api/jobs/{jobId}`: Read one job's progress and results
- `DELETE /api/jobs/{jobId}`: Request cancellation of a queued or processing job
- `GET /api/download/{jobId}`: Download an existing local output of a completed job

## Inputs and job creation

For POST /api/jobs, choose filePath for one local input, files for a list, OR driveInputFolder for a Drive folder ID. Choose outputFolder OR driveOutputFolder. Never mix Drive and local fields on the same side. Local input paths must be absolute existing files on the Desktop computer. outputFolder must be absolute; the server creates it when missing.

Optional fields: preset (exact saved name) and batchCount (positive integer, variations per valid input, default 1). GET /api/presets returns image and video name arrays. A mixed image/video batch needs the preset name in both lists. Omitting preset selects basic defaults, not an arbitrary current UI selection. Processing options belong in the saved Desktop preset.

```json
{
  "filePath": "C:\\Media\\photo.jpg",
  "outputFolder": "C:\\Media\\output",
  "batchCount": 1
}
```

Adapt the example paths for Windows or macOS. Image extensions: .jpg, .jpeg, .png, .webp, .heic, .bmp, .tiff. Video extensions: .mp4, .mov, .avi, .mkv, .webm, .m4v. POST /api/upload takes raw bytes (not multipart), Content-Type: application/octet-stream and required X-Filename. Its 200 response contains filePath, originalName and size; reuse filePath in job creation.

## Submission, progress and outputs

POST /api/jobs returns 202 with jobId, status, inputFiles, batchCount, totalOperations, skipped, position and drive. Save the accepted jobId immediately. Drive input counts may be null until preflight. Inspect skipped; do not silently discard rejected inputs.

There is no submission deduplication: Idempotency-Key is not supported. Send each POST once. On timeout or lost response, inspect GET /api/jobs before any resubmission; do not automatically retry POST or an entire workflow. Keep the accepted jobId available when reporting any later error.

Poll GET /api/jobs/{jobId} roughly every two seconds with a finite deadline, such as ten minutes. queued and processing are in progress. failed and cancelled are not success. A single job exposes result.outputPath or error. A batch exposes results [{file,outputPath}] and errors [{file,error}] when nonempty. completed is not proof of full success: a batch can have partial or even zero successful results. Check errors, successful result count and the accepted skipped list. There is no outcome field.

GET /api/download/{jobId}?index=0 streams the existing local output of a completed job. For batches, index refers to successful results, not original inputs. Preserve original files and use new download names. A client deadline does not cancel the Desktop job. DELETE /api/jobs/{jobId} requests cancellation; inspect status again because current work may finish and existing outputs can remain.

## Limits, errors and retention

One processing job and up to ten waiting jobs. Keep JSON under 64 KiB and uploads at or below 500 MiB. Start with one small file and batchCount: 1. Errors use error text, and sometimes skipped. Handle 400 (input or job state), 401 (key), 404 (route/job/index/output), 412 (Drive configuration/connection), 429 (queue full), 500 or a closed connection (body/upload/disk errors). Do not assume an oversized upload always returns JSON.

GET /api/jobs returns up to 50 recent jobs, queueLength and processing. History is in memory and lost on restart. Terminal entries become eligible for cleanup one hour after completedAt, falling back to createdAt; cleanup runs every ten minutes. DELETE /api/jobs clears terminal history entries. Keep your own job identifiers/results if a workflow needs durable records.

## Google Drive

Configure and connect Drive in Desktop Settings. GET /api/drive/status must show clientConfigured and connected true. Folder fields are IDs accessible to that account, not sharing URLs. Drive transfers use the network. Input files and preset compatibility are resolved after submission; review the actual results.

For Drive output, processing can be completed while upload is still pending. Continue polling until drive.uploadResult or drive.uploadError exists. uploadResult contains uploaded, errors [{name,error}] and optionally cancelled. Inspect them before declaring delivery successful. Read delivered files from Drive; temporary local outputs can be cleaned up and download can return 404. The downloadable local Python/JavaScript starters reject Drive bodies; adapt the workflow explicitly for these transfer states.

## Network access and automation

Any HTTP client can use the same base URL, Bearer authentication, JSON bodies and binary downloads. 127.0.0.1 refers to the machine or container sending the request. A container or remote client needs a reachable address for the Desktop host; a cloud service cannot reach its localhost directly. Remote access requires a private network connection and the corresponding Desktop API settings. Keep the API port off the public internet. File paths belong to the Desktop host: upload bytes or use Drive when the client does not share its filesystem.

Scheduling belongs to the calling application or automation platform. Keep Desktop running, save each accepted jobId and poll with a finite deadline. Coordinate concurrent runs with the queue limit. Do not automatically retry POST /api/jobs or restart an entire workflow after a lost response: each accepted submission creates a new job.

## Official references and working starters

- [API guide](https://metaghost.io/docs/api/1.0.81)
- [OpenAPI JSON](https://metaghost.io/api-docs/1.0.81/openapi.json)
- [OpenAPI YAML](https://metaghost.io/api-docs/1.0.81/openapi.yaml)
- [Python starter](https://metaghost.io/api-docs/1.0.81/quickstart.py)
- [JavaScript starter](https://metaghost.io/api-docs/1.0.81/quickstart.mjs)

OpenAPI 3.1 is the description format. The Desktop version covered by this reference is 1.0.81.

## Complete request and response reference

The following OpenAPI document includes every route, method, authentication requirement, parameter, request body, response and schema for this release.

```json
{
  "openapi": "3.1.0",
  "info": {
    "title": "MetaGhost Desktop 1.0.81 Local API",
    "version": "1.0.81",
    "description": "Released Desktop 1.0.81 API. Preset-driven image/video spoofing with local files or Google Drive. OpenAPI 3.1 describes this file format; the release uses unversioned /api routes."
  },
  "servers": [
    {
      "url": "http://127.0.0.1:3847/api",
      "description": "Default local Desktop server. Replace the port if changed in Settings > API."
    }
  ],
  "security": [
    {
      "LocalApiKey": []
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Check Desktop 1.0.81 and active jobs",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/presets": {
      "get": {
        "summary": "List saved image and video preset names",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Presets"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/drive/status": {
      "get": {
        "summary": "Check the Desktop Google Drive connection",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DriveStatus"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/upload": {
      "post": {
        "summary": "Upload raw image or video bytes",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or incompatible job state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server or upload write error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Raw bytes, not multipart. Maximum 500 MiB (524288000 bytes). Oversized uploads and write errors may close the connection before a JSON error arrives. There is no upload-retention guarantee in 1.0.81.",
        "parameters": [
          {
            "name": "X-Filename",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Name ending in .jpg, .jpeg, .png, .webp, .heic, .bmp, .tiff, .mp4, .mov, .avi, .mkv, .webm or .m4v."
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        }
      }
    },
    "/jobs": {
      "post": {
        "summary": "Create an image/video spoofing job",
        "responses": {
          "202": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcceptedJob"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or incompatible job state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "412": {
            "description": "Google Drive is not configured or connected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Ten jobs already waiting in the queue",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal server or upload write error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Each POST creates a new job. No automatic retries or Idempotency-Key support. JSON body ceiling: 64 KiB; exceeding it may close the connection. Local inputs are checked before acceptance; Drive inputs are discovered afterwards.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpoofJobRequest"
              },
              "example": {
                "filePath": "C:\\Media\\photo.jpg",
                "outputFolder": "C:\\Media\\output",
                "batchCount": 1
              }
            }
          }
        }
      },
      "get": {
        "summary": "List the 50 most recent jobs and queue state",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobList"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "In-memory history. Completed, failed and cancelled jobs become eligible for cleanup after one hour from completedAt, or createdAt when completedAt is absent. Cleanup runs every ten minutes. Restarting Desktop loses this history."
      },
      "delete": {
        "summary": "Clear finished jobs from in-memory history",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cleared": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "cleared"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{jobId}": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9_-]+$"
          }
        }
      ],
      "get": {
        "summary": "Read one job's progress and results",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown job, missing output or unsupported route",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Request cancellation of a queued or processing job",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jobId": {
                      "type": "string"
                    },
                    "status": {
                      "const": "cancelled"
                    }
                  },
                  "required": [
                    "jobId",
                    "status"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or incompatible job state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown job, missing output or unsupported route",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Inspect status again after cancellation. Already-produced files may remain. The currently processing batch file may finish before the queue stops."
      }
    },
    "/download/{jobId}": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9_-]+$"
          }
        }
      ],
      "get": {
        "summary": "Download an existing local output of a completed job",
        "description": "For batches, index addresses the successful results array, not the original input array. A Drive-delivered temporary output may already have been cleaned up and return 404. An unfinished job returns 400.",
        "parameters": [
          {
            "name": "index",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Output bytes; Content-Type follows the file extension and Content-Disposition contains the filename.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Job is not completed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown job, index or output file",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "LocalApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Key generated in Desktop Settings > API."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "skipped": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileError"
            }
          }
        },
        "required": [
          "error"
        ]
      },
      "FileError": {
        "type": "object",
        "properties": {
          "file": {
            "type": "string"
          },
          "error": {
            "type": "string"
          }
        },
        "required": [
          "file",
          "error"
        ]
      },
      "Health": {
        "type": "object",
        "properties": {
          "status": {
            "const": "ok"
          },
          "version": {
            "const": "1.0.81"
          },
          "activeJobs": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "status",
          "version",
          "activeJobs"
        ]
      },
      "Presets": {
        "type": "object",
        "properties": {
          "image": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "video": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "image",
          "video"
        ]
      },
      "DriveStatus": {
        "type": "object",
        "properties": {
          "clientConfigured": {
            "type": "boolean"
          },
          "connected": {
            "type": "boolean"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "scope": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "clientConfigured",
          "connected",
          "email",
          "scope"
        ]
      },
      "DriveUploadResult": {
        "type": "object",
        "properties": {
          "uploaded": {
            "type": "integer",
            "minimum": 0
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "error": {
                  "type": "string"
                }
              },
              "required": [
                "name",
                "error"
              ]
            }
          },
          "cancelled": {
            "type": "boolean"
          }
        },
        "required": [
          "uploaded",
          "errors"
        ]
      },
      "Upload": {
        "type": "object",
        "properties": {
          "filePath": {
            "type": "string",
            "description": "Absolute path on the Desktop computer."
          },
          "originalName": {
            "type": "string"
          },
          "size": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "filePath",
          "originalName",
          "size"
        ]
      },
      "SpoofJobRequest": {
        "type": "object",
        "properties": {
          "filePath": {
            "type": "string",
            "description": "Absolute path to one existing image or video."
          },
          "files": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Absolute path to an existing image or video."
            }
          },
          "driveInputFolder": {
            "type": "string",
            "description": "Folder ID accessible to the connected Desktop Drive account."
          },
          "outputFolder": {
            "type": "string",
            "description": "Absolute local output directory; created when missing."
          },
          "driveOutputFolder": {
            "type": "string",
            "description": "Destination folder ID accessible to the Desktop Drive account."
          },
          "preset": {
            "type": "string",
            "description": "Exact saved preset name for each input media type. Omit for basic defaults."
          },
          "batchCount": {
            "type": "integer",
            "minimum": 1,
            "default": 1,
            "description": "Variations per valid input. Send a positive integer and keep batches small; increase gradually according to your computer's capacity."
          }
        },
        "description": "Preset-driven image/video spoofing only. Choose filePath, files OR driveInputFolder, and outputFolder OR driveOutputFolder. No operation selector or idempotency support. Unknown extra fields are ignored by 1.0.81, not a way to enable another tool.",
        "allOf": [
          {
            "oneOf": [
              {
                "anyOf": [
                  {
                    "required": [
                      "filePath"
                    ]
                  },
                  {
                    "required": [
                      "files"
                    ]
                  }
                ]
              },
              {
                "required": [
                  "driveInputFolder"
                ]
              }
            ]
          },
          {
            "oneOf": [
              {
                "required": [
                  "outputFolder"
                ]
              },
              {
                "required": [
                  "driveOutputFolder"
                ]
              }
            ]
          }
        ]
      },
      "AcceptedJob": {
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "const": "queued"
          },
          "inputFiles": {
            "type": [
              "integer",
              "null"
            ]
          },
          "batchCount": {
            "type": "integer"
          },
          "totalOperations": {
            "type": [
              "integer",
              "null"
            ]
          },
          "skipped": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileError"
            }
          },
          "position": {
            "type": "integer",
            "description": "May be zero when processing has already started."
          },
          "drive": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "input": {
                    "type": "boolean"
                  },
                  "output": {
                    "type": "boolean"
                  }
                }
              }
            ]
          }
        },
        "required": [
          "jobId",
          "status",
          "inputFiles",
          "batchCount",
          "totalOperations",
          "skipped",
          "position",
          "drive"
        ]
      },
      "Output": {
        "type": "object",
        "properties": {
          "outputPath": {
            "type": "string"
          }
        },
        "required": [
          "outputPath"
        ]
      },
      "BatchOutput": {
        "type": "object",
        "properties": {
          "file": {
            "type": "string"
          },
          "outputPath": {
            "type": "string"
          }
        },
        "required": [
          "file",
          "outputPath"
        ]
      },
      "Progress": {
        "type": "object",
        "properties": {
          "phase": {
            "type": "string"
          },
          "phaseProgress": {
            "type": "number"
          },
          "totalProgress": {
            "type": "number"
          },
          "currentFile": {
            "type": "integer"
          },
          "totalFiles": {
            "type": [
              "integer",
              "null"
            ]
          },
          "completedFiles": {
            "type": "integer"
          },
          "fileName": {
            "type": "string"
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "completed",
              "failed",
              "cancelled"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "single",
              "batch"
            ]
          },
          "preset": {
            "type": "string"
          },
          "progress": {
            "$ref": "#/components/schemas/Progress"
          },
          "createdAt": {
            "type": "string"
          },
          "startedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "fileType": {
            "type": "string"
          },
          "fileName": {
            "type": "string"
          },
          "totalFiles": {
            "type": "integer"
          },
          "result": {
            "$ref": "#/components/schemas/Output"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchOutput"
            }
          },
          "error": {
            "type": "string",
            "description": "Single-job failure."
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileError"
            }
          },
          "drive": {
            "type": "object",
            "properties": {
              "inputFolderId": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "outputFolderId": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "uploadResult": {
                "anyOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/DriveUploadResult"
                  }
                ]
              },
              "uploadError": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        },
        "required": [
          "jobId",
          "status",
          "type",
          "preset",
          "progress",
          "createdAt",
          "startedAt",
          "completedAt"
        ],
        "description": "A completed batch may include errors or have no successful results. Check errors and results, plus skipped from POST. For Drive output, processing can reach completed before upload finishes; keep polling until uploadResult or uploadError is present, and inspect uploadResult.errors and uploadResult.cancelled. No outcome field exists in this release."
      },
      "JobList": {
        "type": "object",
        "properties": {
          "jobs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Job"
            }
          },
          "queueLength": {
            "type": "integer"
          },
          "processing": {
            "type": "boolean"
          }
        },
        "required": [
          "jobs",
          "queueLength",
          "processing"
        ]
      }
    }
  }
}
```

## Complete examples

The examples below are the same files available for download from the guide. Python requires 3.10+ and JavaScript requires Node.js 22+. Both use standard libraries. Adapt the paths and configure your local API key before testing.

### quickstart.py

```python
"""MetaGhost Desktop 1.0.81. Python 3.10+. https://metaghost.io/docs/api/1.0.81#quickstart"""
import json
import os
import shutil
import sys
import time
from urllib.error import HTTPError
from urllib.parse import urlparse, quote
from urllib.request import Request, build_opener, HTTPRedirectHandler, ProxyHandler


class NoRedirect(HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        raise RuntimeError("API redirects are not followed; the key stays on the local server.")


def run_job(base_url, api_key, body, timeout_seconds=600, poll_seconds=2, on_accepted=print):
    base = urlparse(base_url)
    if base.scheme != "http" or base.hostname not in ("127.0.0.1", "localhost", "::1") or base.username or base.password or base.query or base.fragment:
        raise ValueError("Run this example on the Desktop computer with an HTTP loopback API URL.")
    if not api_key:
        raise ValueError("Set METAGHOST_API_KEY to the key from Desktop Settings > API.")
    if body.get("driveInputFolder") or body.get("driveOutputFolder"):
        raise ValueError("This starter uses local files and local output. Follow the guide's Drive section for transfer polling and delivery.")
    opener = build_opener(NoRedirect, ProxyHandler({}))

    def request(path, data=None, extra_headers=None, authenticated=True):
        headers = {"Authorization": "Bearer " + api_key} if authenticated else {}
        headers.update(extra_headers or {})
        if data is not None:
            headers["Content-Type"] = "application/json"
        req = Request(base_url.rstrip("/") + path, data=None if data is None else json.dumps(data).encode(), headers=headers)
        try:
            return opener.open(req, timeout=30)
        except HTTPError as error:
            raise RuntimeError(f"HTTP {error.code}: {error.read().decode()}; Retry-After: {error.headers.get('Retry-After', 'not provided')}") from error

    def read_json(path, **kwargs):
        with request(path, **kwargs) as response:
            return json.load(response)

    health = read_json("/health", authenticated=False)
    if health.get("status") != "ok" or health.get("version") != "1.0.81":
        raise RuntimeError("This example targets MetaGhost Desktop 1.0.81. Check the documentation for your installed version before submitting a job.")
    try:
        accepted = read_json("/jobs", data=body)
    except Exception as error:
        raise RuntimeError(f"Submission could not be confirmed. Desktop 1.0.81 does not deduplicate submissions; check GET /api/jobs before submitting again. {error}") from error
    job_id = accepted["jobId"]
    on_accepted(job_id)
    deadline = time.monotonic() + timeout_seconds
    while time.monotonic() < deadline:
        job = read_json("/jobs/" + quote(job_id, safe=""))
        if job["status"] in ("failed", "cancelled"):
            raise RuntimeError(f"Job {job_id}: {job['status']}. {job.get('error') or job.get('errors', [])}")
        if job["status"] == "completed":
            drive = job.get("drive") or {}
            if job.get("errors") or accepted.get("skipped") or drive.get("uploadError") or (drive.get("uploadResult") or {}).get("errors"):
                raise RuntimeError(f"Job {job_id} has partial results. Inspect errors/skipped files before using its output.")
            return job, lambda index: request("/download/" + quote(job_id, safe="") + "?index=" + str(index))
        time.sleep(poll_seconds)
    raise TimeoutError(f"Stopped waiting for {job_id}. The Desktop job was not cancelled; inspect it before retrying.")


if __name__ == "__main__":
    try:
        if len(sys.argv) != 4:
            raise ValueError("Usage: python quickstart.py INPUT_FILE DESKTOP_OUTPUT_FOLDER NEW_DOWNLOAD_FILE")
        source, output_folder, download_file = sys.argv[1:]
        if os.path.exists(download_file):
            raise ValueError("Choose a new download filename. Existing files are never overwritten.")
        body = {"filePath": source, "outputFolder": output_folder, "batchCount": 1}
        if os.environ.get("METAGHOST_PRESET"):
            body["preset"] = os.environ["METAGHOST_PRESET"]
        job, download = run_job(os.environ.get("METAGHOST_API_URL", "http://127.0.0.1:3847/api"), os.environ.get("METAGHOST_API_KEY"), body,
                                on_accepted=lambda job_id: print("Accepted " + job_id, flush=True))
        with download(0) as response, open(download_file, "xb") as destination:
            shutil.copyfileobj(response, destination)
        print(f"Completed {job['jobId']}. Downloaded first output to {download_file}")
    except Exception as error:
        print(str(error), file=sys.stderr)
        sys.exit(1)
```


### quickstart.mjs

```javascript
// MetaGhost Desktop 1.0.81. Node.js 22+. https://metaghost.io/docs/api/1.0.81#quickstart
import { existsSync, createWriteStream } from 'node:fs';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import { pathToFileURL } from 'node:url';

export async function runJob({ baseUrl, apiKey, body, timeoutMs = 600000, pollMs = 2000, onAccepted = () => {} }) {
  const base = new URL(baseUrl);
  if (base.protocol !== 'http:' || !['127.0.0.1', 'localhost', '[::1]'].includes(base.hostname) || base.username || base.password || base.search || base.hash) {
    throw new Error('This example runs on the Desktop computer. Use an HTTP loopback URL without credentials, query or fragment.');
  }
  if (!apiKey) throw new Error('Set METAGHOST_API_KEY to the key shown in Desktop Settings > API.');
  if (body?.driveInputFolder || body?.driveOutputFolder) throw new Error('This starter uses local files and local output. Follow the guide’s Drive section for transfer polling and delivery.');
  const root = base.href.replace(/\/$/, '');
  const request = async (path, options = {}, authenticated = true) => {
    const response = await fetch(`${root}${path}`, {
      ...options, redirect: 'error', signal: AbortSignal.timeout(30000),
      headers: { ...(authenticated ? { Authorization: `Bearer ${apiKey}` } : {}), ...options.headers },
    });
    if (!response.ok) {
      const text = await response.text();
      throw new Error(`HTTP ${response.status}: ${text}; Retry-After: ${response.headers.get('retry-after') || 'not provided'}`);
    }
    return response;
  };
  const health = await (await request('/health', {}, false)).json();
  if (health.status !== 'ok' || health.version !== '1.0.81') throw new Error('This example targets MetaGhost Desktop 1.0.81. Check the documentation for your installed version before submitting a job.');
  let accepted;
  try {
    accepted = await (await request('/jobs', {
      method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
    })).json();
  } catch (error) {
    throw new Error(`Submission could not be confirmed. Desktop 1.0.81 does not deduplicate submissions; check GET /api/jobs before submitting again. ${error.message}`);
  }
  onAccepted(accepted.jobId);
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const job = await (await request(`/jobs/${encodeURIComponent(accepted.jobId)}`)).json();
    if (['failed', 'cancelled'].includes(job.status)) throw new Error(`Job ${accepted.jobId}: ${job.status}. ${job.error || JSON.stringify(job.errors || [])}`);
    if (job.status === 'completed') {
      if (job.errors?.length || accepted.skipped?.length || job.drive?.uploadError || job.drive?.uploadResult?.errors?.length) {
        throw new Error(`Job ${accepted.jobId} has partial results. Inspect errors/skipped files before using its output.`);
      }
      return { job, download: index => request(`/download/${encodeURIComponent(job.jobId)}?index=${index}`) };
    }
    await new Promise(resolve => setTimeout(resolve, pollMs));
  }
  throw new Error(`Stopped waiting for ${accepted.jobId}. The Desktop job was not cancelled; inspect it before retrying.`);
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  const [filePath, outputFolder, downloadFile] = process.argv.slice(2);
  try {
    if (!filePath || !outputFolder || !downloadFile) throw new Error('Usage: node quickstart.mjs INPUT_FILE DESKTOP_OUTPUT_FOLDER NEW_DOWNLOAD_FILE');
    if (existsSync(downloadFile)) throw new Error('Choose a new download filename. Existing files are never overwritten.');
    const result = await runJob({
      baseUrl: process.env.METAGHOST_API_URL || 'http://127.0.0.1:3847/api', apiKey: process.env.METAGHOST_API_KEY,
      body: { filePath, outputFolder, batchCount: 1, ...(process.env.METAGHOST_PRESET ? { preset: process.env.METAGHOST_PRESET } : {}) },
      onAccepted: jobId => console.log(`Accepted ${jobId}`),
    });
    const response = await result.download(0);
    await pipeline(Readable.fromWeb(response.body), createWriteStream(downloadFile, { flags: 'wx' }));
    console.log(`Completed ${result.job.jobId}. Downloaded first output to ${downloadFile}`);
  } catch (error) { console.error(error.message); process.exitCode = 1; }
}
```
