# AutoMask Studio API Integration Guide for AI Agents

Use this file as a context document for an AI coding agent. The goal is to let the agent configure photo processing integration with AutoMask Studio without guessing API behavior.

## What AutoMask Studio Does

AutoMask Studio processes car photos through HTTP API:

- hides license plates using a generated mask;
- replaces the background only by the car/background mask;
- analyzes a photo separately and can classify interior, exterior, podium, and vehicle angle;
- supports preset backgrounds and custom uploaded backgrounds;
- stores original files, masks, and results for 7 days;
- works asynchronously through a job queue;
- can notify an external system through webhook;
- uses a credit balance: successful processing costs credits, failed jobs do not reduce the balance.

## Variables the AI Must Ask For

Before writing integration code, ask the project owner for:

```env
AUTOMASK_BASE_URL=https://automask.ru
AUTOMASK_API_KEY=ak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AUTOMASK_WEBHOOK_URL=https://automask.ru/webhooks/automask
```

If webhook is not available, use polling through `GET /api/v1/jobs/{job_id}`.

## Authentication

Preferred authentication:

```http
X-API-Key: <AUTOMASK_API_KEY>
```

Alternative authentication:

- `api_key` form field;
- `api_key` query parameter for `GET /api/v1/quota`.

Do not expose the API key in frontend JavaScript. Store it on the server side.

## Machine-Readable Contract Summary

Use this section as the primary source of truth when generating code.

```yaml
openapi: 3.0.3
info:
  title: AutoMask Studio API
  version: "1.0"
servers:
  - url: https://automask.ru
security:
  - ApiKeyAuth: []
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
  schemas:
    QueuedJob:
      type: object
      required: [result, queued, code, job_id, status, status_url]
      properties:
        result: {type: boolean, example: true}
        queued: {type: boolean, example: true}
        code: {type: integer, example: 202}
        cost_credits: {type: number, example: 5}
        job_id: {type: string, example: job_abc123}
        status: {type: string, enum: [queued]}
        status_url: {type: string, example: /api/v1/jobs/job_abc123}
        plate_color: {type: string, nullable: true, example: "#111111"}
        plate_smoothing: {type: boolean, example: true}
        plate_mask_only: {type: boolean, example: false}
        webhook_enabled: {type: boolean}
        quota: {type: object}
    JobStatus:
      type: object
      required: [result, job_id, status, code]
      properties:
        result: {type: boolean}
        job_id: {type: string}
        status: {type: string, enum: [queued, processing, success, failed]}
        code: {type: integer}
        message: {type: string}
        url: {type: string, nullable: true}
        plate_mask_url: {type: string, nullable: true}
        background_mask_url: {type: string, nullable: true}
        mask_url: {type: string, nullable: true}
        expires_in_days: {type: integer, example: 7}
        mode: {type: string, enum: [plate, studio_background]}
        background: {type: string}
        plate_color: {type: string, nullable: true, example: "#111111"}
        plate_smoothing: {type: boolean, example: true}
        plate_mask_only: {type: boolean, example: false}
        analysis: {type: object, nullable: true}
        quota: {type: object, nullable: true}
paths:
  /api/v1/process:
    post:
      summary: Queue a photo processing job
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file: {type: string, format: binary}
                url: {type: string, format: uri}
                mode: {type: string, enum: [plate, studio_background], default: plate}
                background: {type: string, enum: [studio, warm, graphite, showroom]}
                background_file: {type: string, format: binary}
                plate_color:
                  type: string
                  description: "License plate fill color. Accepts #RRGGBB, RRGGBB, #RGB, black, white, gray, graphite."
                plate_smoothing:
                  type: boolean
                  description: "When true, softens the license plate mask edge. When false, fills strictly by the raw mask."
                plate_mask_only:
                  type: boolean
                  description: "Separate flag. When true, fill strictly by the detected mask without edge alignment or softening."
                webhook_url: {type: string, format: uri}
      responses:
        "202": {description: Job queued}
  /api/v1/jobs/{job_id}:
    get:
      summary: Get queued job status
      parameters:
        - name: job_id
          in: path
          required: true
          schema: {type: string}
  /api/v1/analyze:
    post:
      summary: Classify a photo without editing it
  /api/v1/webhook:
    get: {summary: Read default webhook URL}
    put: {summary: Set default webhook URL}
    post: {summary: Set default webhook URL}
    patch: {summary: Set default webhook URL}
    delete: {summary: Disable default webhook URL}
  /api/v1/quota:
    get: {summary: Read credit balance}
```

## Business Rules the AI Must Preserve

- Processing (`plate` and `studio_background`) costs exactly `5` credits per successful job.
- Classification (`/api/v1/analyze`) costs exactly `1` credit per successful request.
- Master API key is for backend/internal use and is not charged.
- Failed processing jobs must not be treated as completed user results.
- If `mode=studio_background` returns `status=success`, treat the job as successful even when a license plate was not detected. Background replacement and plate masking are separate capabilities.
- If `mode=plate` fails because the plate was not detected, show a clear user-facing message and do not pretend the image was processed.
- Optional `plate_color` changes only the license plate fill color. Invalid color values return `400`.
- API keys can store a default license plate fill color. If a request omits `plate_color`, AutoMask uses the key default; a request-level `plate_color` overrides it.
- API keys can store default license plate edge smoothing. If a request omits `plate_smoothing`, AutoMask uses the key default. Default is `true`.
- API keys can store the separate `plate_mask_only` flag. If true, the plate is filled strictly by the detected mask. Default is `false`.
- Result files, masks and originals are temporary. Store or proxy them if your product needs access after 7 days.
- Webhook delivery can be repeated. Always process webhook payloads idempotently by `job_id`.
- Never expose `AUTOMASK_API_KEY` in frontend JavaScript, mobile app bundles, public logs or public repositories.

## Main Workflow

1. Optionally configure default webhook:
   `PUT /api/v1/webhook`
2. Optionally analyze a photo before processing:
   `POST /api/v1/analyze`
3. Submit a photo:
   `POST /api/v1/process`
4. Store returned `job_id`.
5. Wait for final result:
   - either receive webhook;
   - or poll `GET /api/v1/jobs/{job_id}` every 2-3 seconds.
6. Save result URLs returned by the final payload.

## Endpoints

### Analyze Photo

`POST /api/v1/analyze`

Use this endpoint when the integration only needs to understand what is on the photo. It does not create an edited image and does not enqueue a processing job.

Cost:

- fixed `1` credit per successful request;
- master API key is not charged.

Content type:

```http
multipart/form-data
```

Headers:

```http
X-API-Key: <AUTOMASK_API_KEY>
```

Fields:

| Field | Required | Description |
|---|---:|---|
| `file` | yes, if `url` is absent | Source photo. JPG, PNG, WebP. |
| `url` | yes, if `file` is absent | Remote source photo URL. |

Successful response:

```json
{
  "result": true,
  "code": 200,
  "cost_credits": 1,
  "analysis": {
    "photo_type": {
      "value": "exterior",
      "label": "Экстерьер",
      "source_class": "car-front",
      "conf": 92
    },
    "view": {
      "value": "front",
      "label": "Спереди",
      "source_class": "car-front",
      "conf": 92
    },
    "detections": {
      "car": {"id": 0, "conf": 96},
      "license-plate": {"id": 2, "conf": 88}
    },
    "background_detections": {
      "car-front": {"id": 2, "conf": 92}
    }
  }
}
```

Possible `photo_type.value` values:

- `interior`
- `exterior`
- `podium`
- `unknown`

Possible `view.value` values:

- `front`
- `back`
- `left`
- `right`
- `three-quarter-left`
- `three-quarter-right`
- `overhead-left`
- `overhead-right`

### Submit Processing Job

`POST /api/v1/process`

Cost:

- fixed `5` credits per successful processing job;
- master API key is not charged.

Content type:

```http
multipart/form-data
```

Headers:

```http
X-API-Key: <AUTOMASK_API_KEY>
```

Fields:

| Field | Required | Description |
|---|---:|---|
| `file` | yes, if `url` is absent | Source photo. JPG, PNG, WebP. |
| `url` | yes, if `file` is absent | Remote source photo URL. |
| `mode` | no | `plate` or `studio_background`. Default: `plate`. |
| `background` | no | Preset: `studio`, `warm`, `graphite`, `showroom`. |
| `background_file` | no | Custom background image. Overrides preset for this request. |
| `plate_color` | no | License plate fill color. Accepts `#RRGGBB`, `RRGGBB`, `#RGB`, `black`, `white`, `gray`, `graphite`. Overrides the API key default for this request. |
| `plate_smoothing` | no | Soft plate mask edge. Accepts `true`, `false`, `1`, `0`, `smooth`, `raw`. Overrides the API key default for this request. |
| `plate_mask_only` | no | Separate flag. If `true`, fills strictly by the detected mask without edge alignment or softening. Overrides the API key default for this request. |
| `webhook_url` | no | One-time webhook URL for this job. Overrides key default webhook. |

Recommended modes:

- Use `plate` when only license plate masking is needed.
- Use `studio_background` when the car background must be replaced.

Successful queued response:

```json
{
  "result": true,
  "queued": true,
  "code": 202,
  "message": "Задача поставлена в очередь.",
  "cost_credits": 5,
  "job_id": "job_abc123",
  "status": "queued",
  "status_url": "/api/v1/jobs/job_abc123",
  "plate_color": "#111111",
  "plate_smoothing": true,
  "plate_mask_only": false,
  "webhook_enabled": true
}
```

Final successful job payload contains an `analysis` object with the same structure as `/api/v1/analyze`, alongside result URLs and masks.

### Check Job Status

`GET /api/v1/jobs/{job_id}`

Headers:

```http
X-API-Key: <AUTOMASK_API_KEY>
```

Possible statuses:

- `queued`
- `processing`
- `success`
- `failed`

Final success response includes:

```json
{
  "result": true,
  "job_id": "job_abc123",
  "status": "success",
  "code": 200,
  "url": "/results/result.webp",
  "plate_mask_url": "/masks/result_mask_lp.png",
  "background_mask_url": "/masks/result_mask_bg.png",
  "mask_url": "/masks/result_mask_bg.png",
  "expires_in_days": 7,
  "mode": "studio_background",
  "background": "custom",
  "plate_color": "#111111",
  "plate_smoothing": true,
  "plate_mask_only": false
}
```

Build absolute file URLs by prefixing `AUTOMASK_BASE_URL`.

Final failed response includes:

```json
{
  "result": false,
  "job_id": "job_abc123",
  "status": "failed",
  "code": 803,
  "message": "License plate was not detected",
  "mode": "plate",
  "analysis": {
    "photo_type": {"value": "exterior", "label": "Exterior", "conf": 97},
    "view": {"value": "three-quarter-left", "label": "Three-quarter left", "conf": 97}
  },
  "quota": {
    "remaining": 40,
    "credits_remaining": 40
  }
}
```

Do not rely on `message` text for business logic. Use `status`, `result`, and numeric `code`.

### Configure Default Webhook

`GET /api/v1/webhook`

Returns current default webhook for the API key.

`PUT /api/v1/webhook`

JSON body:

```json
{
  "webhook_url": "https://automask.ru/webhooks/automask"
}
```

`DELETE /api/v1/webhook`

Disables default webhook for the API key.

Invalid webhook URLs return `422`. URL must start with `http://` or `https://`.

### Check Balance

`GET /api/v1/quota`

Headers:

```http
X-API-Key: <AUTOMASK_API_KEY>
```

Response:

```json
{
  "result": true,
  "quota": {
    "active": true,
    "authorized": true,
    "remaining": 50,
    "credits_remaining": 50,
    "user_credits_remaining": 50,
    "process_credit_cost": 5,
    "classification_credit_cost": 1,
    "used_total": 0
  }
}
```

## Webhook Receiver Contract

AutoMask sends `POST` with JSON payload equal to the final job status response.

Headers:

```http
X-AutoMask-Job: job_abc123
Content-Type: application/json
```

Receiver requirements:

- Return HTTP `2xx` quickly.
- Store `job_id`, `status`, `result`, `url`, `mask_url`, and error fields.
- Make webhook handler idempotent. The same job can be safely processed more than once.
- Do not block webhook response while downloading large files.

Example webhook request:

```http
POST /webhooks/automask HTTP/1.1
Host: client-site.example.com
Content-Type: application/json
X-AutoMask-Job: job_abc123
```

```json
{
  "result": true,
  "job_id": "job_abc123",
  "status": "success",
  "code": 200,
  "url": "/results/result.webp",
  "mask_url": "/masks/result_mask_bg.png",
  "plate_mask_url": "/masks/result_mask_lp.png",
  "background_mask_url": "/masks/result_mask_bg.png",
  "expires_in_days": 7,
  "mode": "studio_background",
  "background": "custom",
  "analysis": {
    "photo_type": {"value": "exterior", "label": "Exterior", "conf": 97},
    "view": {"value": "three-quarter-left", "label": "Three-quarter left", "conf": 97}
  }
}
```

Recommended idempotent webhook pseudocode:

```text
function handleAutoMaskWebhook(payload, headers):
    job_id = headers["X-AutoMask-Job"] or payload["job_id"]
    existing = db.automask_jobs.find_by_job_id(job_id)
    if existing and existing.status in ["success", "failed"]:
        return 200

    db.automask_jobs.upsert(
        job_id=job_id,
        status=payload.status,
        result_url=payload.url,
        mask_url=payload.mask_url,
        error_code=payload.code if payload.result == false else null,
        error_message=payload.message,
        payload_json=payload
    )
    return 200
```

## Suggested Database Model

Use a local table like this in the integrating project:

```sql
create table automask_jobs (
  id integer primary key,
  local_file_id text,
  job_id text not null unique,
  mode text not null,
  status text not null,
  result_url text,
  mask_url text,
  plate_mask_url text,
  background_mask_url text,
  error_code integer,
  error_message text,
  analysis_json text,
  payload_json text,
  expires_at text,
  created_at text not null,
  updated_at text not null
);
```

Minimum fields the integration must store:

- local object ID or local file ID;
- AutoMask `job_id`;
- current `status`;
- final `result_url` and `mask_url`;
- `error_code` and `error_message`;
- raw final payload for diagnostics.

## Python Integration Example

```python
import time
import requests

BASE_URL = "https://automask.ru"
API_KEY = "ak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

headers = {"X-API-Key": API_KEY}

with open("car.jpg", "rb") as photo:
    response = requests.post(
        f"{BASE_URL}/api/v1/process",
        headers=headers,
        data={
            "mode": "studio_background",
            "background": "graphite",
            "plate_color": "#111111",
            "plate_smoothing": "true",
            "plate_mask_only": "false",
        },
        files={"file": photo},
        timeout=120,
    )

payload = response.json()
if not payload.get("result"):
    raise RuntimeError(payload)

job_id = payload["job_id"]

while True:
    job = requests.get(
        f"{BASE_URL}/api/v1/jobs/{job_id}",
        headers=headers,
        timeout=30,
    ).json()
    if job["status"] in {"success", "failed"}:
        break
    time.sleep(2)

if job.get("result"):
    result_url = BASE_URL + job["url"]
    mask_url = BASE_URL + job["mask_url"]
    print(result_url, mask_url)
else:
    raise RuntimeError(job)
```

## Node.js Integration Example

Server-side Node.js example with polling:

```js
import fs from "node:fs";
import { setTimeout as sleep } from "node:timers/promises";
import FormData from "form-data";
import fetch from "node-fetch";

const BASE_URL = process.env.AUTOMASK_BASE_URL;
const API_KEY = process.env.AUTOMASK_API_KEY;

export async function submitAutoMaskJob(filePath, options = {}) {
  const form = new FormData();
  form.append("file", fs.createReadStream(filePath));
  form.append("mode", options.mode || "studio_background");
  if (options.background) form.append("background", options.background);
  if (options.plateColor) form.append("plate_color", options.plateColor);
  if (options.plateSmoothing !== undefined) form.append("plate_smoothing", options.plateSmoothing ? "true" : "false");
  if (options.plateMaskOnly !== undefined) form.append("plate_mask_only", options.plateMaskOnly ? "true" : "false");
  if (options.webhookUrl) form.append("webhook_url", options.webhookUrl);

  const response = await fetch(`${BASE_URL}/api/v1/process`, {
    method: "POST",
    headers: {"X-API-Key": API_KEY, ...form.getHeaders()},
    body: form,
  });
  const payload = await response.json();
  if (!response.ok || !payload.result) throw new Error(JSON.stringify(payload));
  return payload;
}

export async function waitAutoMaskJob(jobId) {
  for (;;) {
    const response = await fetch(`${BASE_URL}/api/v1/jobs/${jobId}`, {
      headers: {"X-API-Key": API_KEY},
    });
    const job = await response.json();
    if (job.status === "success" || job.status === "failed") return job;
    await sleep(2000);
  }
}
```

Express webhook receiver:

```js
import express from "express";

const app = express();
app.use(express.json({limit: "2mb"}));

app.post("/webhooks/automask", async (req, res) => {
  const jobId = req.header("X-AutoMask-Job") || req.body.job_id;
  if (!jobId) return res.status(400).json({result: false});

  const payload = req.body;
  // Idempotent upsert by jobId. Do not create duplicates on retries.
  await saveAutoMaskPayload(jobId, {
    status: payload.status,
    resultUrl: payload.url ? process.env.AUTOMASK_BASE_URL + payload.url : null,
    maskUrl: payload.mask_url ? process.env.AUTOMASK_BASE_URL + payload.mask_url : null,
    errorCode: payload.result ? null : payload.code,
    errorMessage: payload.message || null,
    rawPayload: payload,
  });

  return res.status(200).json({result: true});
});
```

## PHP Integration Example

```php
<?php
$baseUrl = "https://automask.ru";
$apiKey = "ak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

$ch = curl_init($baseUrl . "/api/v1/process");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["X-API-Key: " . $apiKey],
    CURLOPT_POSTFIELDS => [
        "mode" => "studio_background",
        "background" => "showroom",
        "plate_color" => "#111111",
        "plate_smoothing" => "true",
        "plate_mask_only" => "false",
        "file" => new CURLFile(__DIR__ . "/car.jpg"),
    ],
]);

$payload = json_decode(curl_exec($ch), true);
curl_close($ch);

$jobId = $payload["job_id"];

do {
    sleep(2);
    $poll = curl_init($baseUrl . "/api/v1/jobs/" . $jobId);
    curl_setopt_array($poll, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ["X-API-Key: " . $apiKey],
    ]);
    $job = json_decode(curl_exec($poll), true);
    curl_close($poll);
} while (!in_array($job["status"], ["success", "failed"], true));

if (!empty($job["result"])) {
    echo $baseUrl . $job["url"] . PHP_EOL;
} else {
    throw new RuntimeException($job["message"] ?? "AutoMask failed");
}
```

## AI Agent Implementation Checklist

When using this file inside an AI coding tool, instruct the AI to:

1. Create server-side configuration for `AUTOMASK_BASE_URL` and `AUTOMASK_API_KEY`.
2. Add a server-side function `submitAutoMaskJob(photo, options)`.
3. Store `job_id` in the local database.
4. Implement either webhook receiver or polling worker.
5. Save final result URLs and expiration date.
6. Show user-facing statuses: queued, processing, success, failed.
7. Never put the API key into browser code.
8. Handle `401`, `402`, `415`, `422`, and `500`.
9. Make webhook processing idempotent by `job_id`.
10. Download or proxy result files only if the local product needs permanent storage. AutoMask stores them for 7 days.
11. Add an admin/debug view or logs for job status, payload, webhook attempts and errors.
12. Add tests for success, failed job, low balance, invalid API key and duplicate webhook delivery.

## Security Checklist

- Store `AUTOMASK_API_KEY` only in server-side env/secrets.
- Do not print the full API key in logs. Mask it as `ak_...abcd`.
- Validate upload size and MIME type before sending to AutoMask.
- Use HTTPS for `AUTOMASK_BASE_URL` and webhook URLs.
- Rate-limit public upload endpoints in the integrating product.
- Keep raw webhook payloads for support, but avoid logging private user data unnecessarily.
- If your product exposes result files to end users, authorize access through your own application.

## Prompt for ChatGPT, Claude, Cursor, or Copilot

Copy this prompt together with this Markdown file:

```text
You are integrating AutoMask Studio into my project.
Read the attached AutoMask API guide and implement server-side photo processing.
Ask me for AUTOMASK_BASE_URL, AUTOMASK_API_KEY, and whether I want webhook or polling.
Do not expose the API key in frontend code.
Implement:
- detect the backend framework and follow its existing patterns;
- create an AutoMask client/service module;
- submit photo to /api/v1/process;
- save job_id;
- check /api/v1/jobs/{job_id} or receive webhook;
- save result URL, mask URL, status, and error message;
- save analysis/photo classification data when it is present;
- show statuses to users;
- handle low balance and API errors.
- make webhook processing idempotent by job_id;
- add tests or at least a manual test script for success, failure, low balance and duplicate webhook.
Use the existing framework patterns in this project.
```

## Common Error Handling

| HTTP/code | Meaning | Recommended handling |
|---|---|---|
| `400` | Bad request or remote URL download failed | Ask user to upload a local image. |
| `401` | API key missing, invalid, or disabled | Check server env and key status. |
| `402` | Not enough credits on balance | Stop queueing new jobs and ask to top up balance. |
| `415` | Unsupported file type | Accept only JPG, PNG, WebP. |
| `422 / 800` | Model could not classify photo | Ask for clearer car photo. |
| `422 / 802` | Background mask failed | Retry with another image or use plate mode. |
| `422 / 803` | Plate detection failed | Continue only if plate masking is not required. |
| `500` | Server error | Retry later and log payload. |

## Notes for Production

- Use HTTPS for `AUTOMASK_BASE_URL` and webhook URLs.
- Keep API keys in secrets manager or `.env`.
- Store `job_id` and final payload in your database.
- Use background workers for polling.
- Verify webhook source at application level if your environment requires it.
- Treat result URLs as temporary because files expire after 7 days.
