{
  "openapi": "3.1.1",
  "info": {
    "title": "BytePhase Integration API",
    "version": "1.0.0",
    "summary": "Send leads and self check-ins into a BytePhase workspace, and read recent activity out of it.",
    "description": "The BytePhase Integration API is the surface a website form, a form builder, a lead\nmarketplace or an automation tool (Zapier, n8n, a custom script) uses to talk to a\nBytePhase workspace.\n\nIt does two things:\n\n- **Intake** — create a **lead** or a **self check-in** (a customer booking a device in\n  before they reach the counter). Every intake route runs the same pipeline the app\n  uses, so a lead created here gets the same follow-up assignment, notifications and\n  automations as one typed in at the counter.\n- **Zapier reads** — list the most recent repairs, customers, leads, payments, repair\n  status changes and outsourced repairs, and search customers. These exist for the\n  BytePhase Zapier app and are documented so you can build your own polling integrations\n  on the same contract.\n\nEverything else in BytePhase — invoices, inventory, employees, settings, billing — is\nnot part of this API.\n\n## Base URL\n\nThe API is served **per workspace**. Your workspace subdomain is part of the host:\n\n```\nhttps://{workspace}.api.bytephase.com/api\n```\n\nA workspace called `harbor-repair` talks to\n`https://harbor-repair.api.bytephase.com/api/integrations/ping`. Getting this wrong\nis the most common setup mistake, so call `GET /integrations/ping` first: it confirms the\nkey, the host and the workspace in one request.\n\nWhite-label workspaces on their own domain use `https://{custom_domain}/api` instead.\nPick the matching server from the list below.\n\n## Authentication\n\nCreate an API key in the BytePhase portal under **Settings → Integrations**. Keys start\nwith `bp_`. Send it on every request, preferably as a bearer token:\n\n```\nAuthorization: Bearer bp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n```\n\nTools that cannot set `Authorization` may send `X-API-Key: bp_…` instead. A key is tied\nto the provider you chose when you created it (WordPress, Website, REST API, Zapier,\nTypeform, IndiaMART, …). Intake routes accept any provider's key; the Zapier read routes\naccept **only** a Zapier key, because they read business data. Do not reuse a Zapier key\nfor a website form.\n\nKeys can be disabled, given an expiry, rotated and revealed again from the portal. A\ndisabled or expired key is rejected with `401`.\n\n## Businesses\n\nA workspace may run more than one business (branch). Name the business a submission\nbelongs to with `business_id` in the request body, or with an `X-Business` header. Omit\nboth and the workspace's default business is used. Every response carries the resolved\nbusiness in an `X-Business` response header.\n\n## Idempotency\n\nIntake routes honour an `Idempotency-Key` header. Send a fresh UUID per logical\nsubmission and reuse it on retry: a replay returns the original outcome with status\n`duplicate` and the original `request_id` instead of creating a second record. On the\nprovider ingest route a key is derived automatically when the provider sends none.\nDetails are on each operation.\n\n## Responses\n\n- Intake routes return one envelope: `{ \"status\", \"request_id\", \"data\" }`, where `status`\n  is `completed` (201), `duplicate` (200) or `received` (202).\n- Zapier read routes return a **bare JSON array**, which is what the Zapier platform\n  requires of a polling trigger. There is no envelope and no pagination cursor; use the\n  `limit` parameter.\n- Errors return `{ \"error\": { \"code\", \"message\", \"details\", \"request_id\",\n  \"documentation_url\" }, \"message\" }`. `error.code` is stable and machine-readable;\n  `message` is mirrored at the top level for older clients. Every response carries an\n  `X-Request-Id` header — quote it when contacting support.\n- Timestamps are UTC, ISO 8601, with a `Z` suffix and microseconds, for example\n  `2026-09-09T09:14:32.000000Z`. Convert to the workspace's timezone yourself.\n- Fields are `snake_case`. Ignore fields you do not recognise; new ones may be added\n  without notice. Removing or renaming a field is a breaking change and will not happen\n  without a deprecation period.\n\n## Rate limits\n\nEach API key may make **60 requests per minute**. Every response carries\n`X-RateLimit-Limit` and `X-RateLimit-Remaining`; a `429` also carries `Retry-After`.\nBack off exponentially on `429`, and never retry any other `4xx`.\n\n## Reporting a problem\n\nThis specification is public. If a response you receive does not match what is described\nhere, open an issue on the `bytephase-developers` repository with the `X-Request-Id`.\n",
    "termsOfService": "https://bytephase.com/terms",
    "contact": {
      "name": "BytePhase developer support",
      "url": "https://developers.bytephase.com",
      "email": "support@bytephase.com"
    },
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    }
  },
  "servers": [
    {
      "url": "https://{workspace}.api.bytephase.com/api",
      "description": "Production",
      "variables": {
        "workspace": {
          "default": "harbor-repair",
          "description": "Your workspace subdomain — the part before `.bytephase.com` in your BytePhase URL."
        }
      }
    },
    {
      "url": "https://{workspace}.api.bytephaserecovery.com/api",
      "description": "Staging (BytePhase's own pre-release environment — not a customer sandbox)",
      "variables": {
        "workspace": {
          "default": "harbor-repair",
          "description": "Your workspace subdomain on staging."
        }
      }
    },
    {
      "url": "http://{workspace}.localhost:81/api",
      "description": "Local development",
      "variables": {
        "workspace": {
          "default": "demo",
          "description": "A workspace subdomain on a local BytePhase stack."
        }
      }
    },
    {
      "url": "https://{custom_domain}/api",
      "description": "White-label custom domain",
      "variables": {
        "custom_domain": {
          "default": "repairs.example.com",
          "description": "Your own domain, when your workspace is served white-label. Do not use `api.bytephase.com`."
        }
      }
    }
  ],
  "security": [
    {
      "BearerApiKey": []
    },
    {
      "HeaderApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Connection",
      "description": "Prove that a key, a host and a workspace line up before you send anything.\nBoth ping routes are read-only and safe to call as often as you like within the rate limit.\n"
    },
    {
      "name": "Intake",
      "description": "Create leads and self check-ins. Four routes reach the same pipeline:\n\n| Route | Use it when |\n|---|---|\n| `POST /integrations/leads` | You control the payload and want a lead. Strict validation. |\n| `POST /integrations/self-checkin` | You control the payload and want a self check-in. Strict validation. |\n| `POST /integrations/submit` | You send one canonical envelope and let the portal's form configuration decide the destination and field mapping. |\n| `POST /integrations/ingest/{adapter}` | A third-party provider (Typeform, JotForm, Tally.so, IndiaMART, Justdial, a plain HTML form) posts its own payload shape directly to BytePhase. |\n| `GET /integrations/custom-fields` | You want to render the shop's own custom fields on your form before sending any of the above. |\n\nAll four honour `Idempotency-Key`, log every submission in the portal's activity screen, and return the same\n`{status, request_id, data}` envelope.\n"
    },
    {
      "name": "Zapier",
      "description": "The routes behind the BytePhase Zapier app. They require a key whose provider is **Zapier**.\n\nThe list routes are polling triggers: each returns the most recent records as a **bare JSON array** (no\nenvelope), newest first, capped by `limit`. There is no cursor; a poller de-duplicates on `id`. A burst larger\nthan `limit` between two polls will be missed, so poll often or keep `limit` high.\n\nErrors take the same standard shape as the rest of the API.\n"
    }
  ],
  "paths": {
    "/integrations/ping": {
      "get": {
        "operationId": "ping",
        "tags": [
          "Connection"
        ],
        "summary": "Test the connection",
        "description": "Confirms in one request that your API key, the host and the workspace all line up. Use it first, before any\nwrite: a wrong subdomain, a disabled key or a key for another workspace all show up here rather than as a\nmysterious failure later.\n\nAny provider's key is accepted. The response names the workspace and the provider the key was created for.\nRead-only; nothing is created or changed.\n",
        "x-codeSamples": [
          {
            "lang": "cURL",
            "label": "cURL",
            "source": "#!/usr/bin/env sh\n# Replace your-workspace with your workspace subdomain and bp_your_api_key with a key\n# from Settings -> Integrations in the BytePhase portal.\ncurl -sS -w '\\nHTTP %{http_code}\\n' \\\n  \"https://your-workspace.api.bytephase.com/api/integrations/ping\" \\\n  -H \"Authorization: Bearer bp_your_api_key\" \\\n  -H \"Accept: application/json\"\n"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript",
            "source": "// Node.js 18+ (built-in fetch). Replace the two placeholders and run: node ping.js\nconst WORKSPACE = \"your-workspace\";\nconst API_KEY = \"bp_your_api_key\";\n\nasync function main() {\n  const response = await fetch(`https://${WORKSPACE}.api.bytephase.com/api/integrations/ping`, {\n    headers: {\n      Authorization: `Bearer ${API_KEY}`,\n      Accept: \"application/json\",\n    },\n  });\n\n  const body = await response.json();\n\n  if (!response.ok) {\n    // Every error carries a stable code and a request id you can quote to support.\n    console.error(`Failed (${response.status}) ${body.error?.code ?? \"\"}: ${body.message}`);\n    console.error(`Request id: ${response.headers.get(\"X-Request-Id\")}`);\n    process.exit(1);\n  }\n\n  console.log(`Connected to \"${body.workspace}\" as a ${body.provider} key`);\n}\n\nmain().catch((error) => {\n  console.error(error.message);\n  process.exit(1);\n});\n"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n// Plain PHP with the curl extension. Replace the two placeholders and run: php ping.php\n$workspace = 'your-workspace';\n$apiKey = 'bp_your_api_key';\n\n$ch = curl_init(\"https://{$workspace}.api.bytephase.com/api/integrations/ping\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$apiKey}\",\n        'Accept: application/json',\n    ],\n]);\n\n$raw = curl_exec($ch);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n\n$body = json_decode($raw, true) ?? [];\n\nif ($status !== 200) {\n    // Every error carries a stable code and a request id you can quote to support.\n    fwrite(STDERR, sprintf(\n        \"Failed (%d) %s: %s (request id %s)\\n\",\n        $status,\n        $body['error']['code'] ?? '',\n        $body['message'] ?? $raw,\n        $body['error']['request_id'] ?? 'unknown'\n    ));\n    exit(1);\n}\n\nprintf(\"Connected to \\\"%s\\\" as a %s key\\n\", $body['workspace'], $body['provider']);\n"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "# Python 3 with requests (pip install requests). Replace the two placeholders and run: python3 ping.py\nimport sys\n\nimport requests\n\nWORKSPACE = \"your-workspace\"\nAPI_KEY = \"bp_your_api_key\"\n\nresponse = requests.get(\n    f\"https://{WORKSPACE}.api.bytephase.com/api/integrations/ping\",\n    headers={\"Authorization\": f\"Bearer {API_KEY}\", \"Accept\": \"application/json\"},\n    timeout=15,\n)\nbody = response.json()\n\nif not response.ok:\n    # Every error carries a stable code and a request id you can quote to support.\n    error = body.get(\"error\", {})\n    print(f\"Failed ({response.status_code}) {error.get('code', '')}: {body.get('message')}\", file=sys.stderr)\n    print(f\"Request id: {response.headers.get('X-Request-Id')}\", file=sys.stderr)\n    sys.exit(1)\n\nprint(f'Connected to \"{body[\"workspace\"]}\" as a {body[\"provider\"]} key')\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Connected.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Business": {
                "$ref": "#/components/headers/XBusiness"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectionStatus"
                },
                "examples": {
                  "Connected": {
                    "$ref": "#/components/examples/Ping"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/integrations/custom-fields": {
      "get": {
        "operationId": "listCustomFields",
        "tags": [
          "Intake"
        ],
        "summary": "List the shop's custom fields",
        "description": "Read the custom fields the shop has added to its own forms, so your website form can render **their**\nfields instead of inventing its own. A value you send back under a listed `field_name` — as a top-level\nintake field or a `custom_fields` key — lands in that custom field on the lead or self check-in, exactly as\nif it had been typed in at the counter. Read-only; any provider's key is accepted.\n\nCall it twice: once without `form_type` to discover which of the shop's forms have custom fields configured\n(`form_types`), then with `?form_type=` to get that form's definitions (`fields`). Form types are the\nshop's own names; `Lead` is the one that matters for `POST /integrations/leads`.\n\nDefinitions change only when the shop edits its settings, so cache the result for a while rather than\ncalling it on every page view.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/FormTypeQuery"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          }
        ],
        "responses": {
          "200": {
            "description": "Without `form_type`, the forms that have custom fields; with it, that form's field definitions.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Business": {
                "$ref": "#/components/headers/XBusiness"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/CustomFieldFormTypes"
                    },
                    {
                      "$ref": "#/components/schemas/CustomFieldDefinitions"
                    }
                  ]
                },
                "examples": {
                  "Without form_type": {
                    "$ref": "#/components/examples/CustomFieldFormTypes"
                  },
                  "With form_type=Lead": {
                    "$ref": "#/components/examples/CustomFieldDefinitions"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/integrations/submit": {
      "post": {
        "operationId": "submitForm",
        "tags": [
          "Intake"
        ],
        "summary": "Submit a form",
        "description": "Send one canonical envelope — `form_id`, an optional `destination`, and the form's fields under `data` —\nand let the form's configuration in the portal decide the rest. This is the route the BytePhase WordPress\nplugin uses, and the right one when a site owner (not a developer) will map fields and choose the\ndestination from the portal after you have wired the form up.\n\n**How the destination is chosen.** `destination` in the body (or `?destination=`) wins. Otherwise the form\nconfigured for `form_id` decides. If the key has exactly one form configured and you send neither, that form\nis used. If nothing decides it, the submission is parked with `202 received` and can be replayed from the\nportal once the form is configured.\n\n**Validation is lenient on this route.** Only `data` must be present. After the form's field mapping is\napplied, values are trimmed, empty strings dropped, `yes`/`no` turned into booleans and over-long values cut\nto their limit. An optional field that still fails a rule is moved to custom fields rather than rejecting\nthe whole submission. Only a missing `name`, or neither `email` nor `mobile_number`, returns `422`.\n\n**Duplicates** follow the destination's default (leads are matched on mobile or email and returned with\n`duplicate`; self check-ins are always created) unless the form's policy says otherwise.\n\n**Idempotency.** Send an `Idempotency-Key` and reuse it on retry; see the header.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          },
          {
            "$ref": "#/components/parameters/DestinationQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitRequest"
              },
              "examples": {
                "Contact form mapped in the portal": {
                  "$ref": "#/components/examples/SubmitLeadRequest"
                },
                "Destination forced": {
                  "$ref": "#/components/examples/SubmitSelfCheckInRequest"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "label": "cURL",
            "source": "#!/usr/bin/env sh\n# Replace your-workspace and bp_your_api_key. The field names under \"data\" are the form's own;\n# the mapping configured for \"website-contact\" in the portal turns them into lead fields.\ncurl -sS -w '\\nHTTP %{http_code}\\n' -X POST \\\n  \"https://your-workspace.api.bytephase.com/api/integrations/submit\" \\\n  -H \"Authorization: Bearer bp_your_api_key\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"form_id\": \"website-contact\",\n    \"data\": {\n      \"full_name\": \"Jordan Ellis\",\n      \"phone\": \"4155550142\",\n      \"email\": \"jordan.ellis@example.com\",\n      \"message\": \"Screen cracked, touch still works. Need a quote for a replacement.\",\n      \"Preferred contact time\": \"After 6 pm\"\n    }\n  }'\n"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript",
            "source": "// Node.js 18+ (built-in fetch). Replace the two placeholders and run: node submit-form.js\nconst { randomUUID } = require(\"node:crypto\");\n\nconst WORKSPACE = \"your-workspace\";\nconst API_KEY = \"bp_your_api_key\";\n\n// The field names under \"data\" are the form's own. The mapping configured for\n// \"website-contact\" in the portal turns them into lead fields; anything unmapped\n// is kept as a custom field.\nconst submission = {\n  form_id: \"website-contact\",\n  data: {\n    full_name: \"Jordan Ellis\",\n    phone: \"4155550142\",\n    email: \"jordan.ellis@example.com\",\n    message: \"Screen cracked, touch still works. Need a quote for a replacement.\",\n    \"Preferred contact time\": \"After 6 pm\",\n  },\n};\n\nasync function main() {\n  const response = await fetch(`https://${WORKSPACE}.api.bytephase.com/api/integrations/submit`, {\n    method: \"POST\",\n    headers: {\n      Authorization: `Bearer ${API_KEY}`,\n      \"Content-Type\": \"application/json\",\n      Accept: \"application/json\",\n      \"Idempotency-Key\": randomUUID(),\n    },\n    body: JSON.stringify(submission),\n  });\n\n  const body = await response.json();\n\n  if (!response.ok) {\n    console.error(`Failed (${response.status}) ${body.error?.code ?? \"\"}: ${body.message}`);\n    for (const detail of body.error?.details ?? []) {\n      console.error(`  ${detail.field}: ${detail.message}`);\n    }\n    console.error(`Request id: ${response.headers.get(\"X-Request-Id\")}`);\n    process.exit(1);\n  }\n\n  if (body.status === \"received\") {\n    // 202: the form is not configured in the portal yet. The submission is parked and can be\n    // replayed from the portal once it is; do not retry.\n    console.log(`Parked until form \"${submission.form_id}\" is configured (request ${body.request_id})`);\n  } else {\n    console.log(`${body.status}: ${body.data.object} #${body.data.id} (request ${body.request_id})`);\n  }\n}\n\nmain().catch((error) => {\n  console.error(error.message);\n  process.exit(1);\n});\n"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n// Plain PHP with the curl extension. Replace the two placeholders and run: php submit-form.php\n$workspace = 'your-workspace';\n$apiKey = 'bp_your_api_key';\n\n// The field names under \"data\" are the form's own. The mapping configured for\n// \"website-contact\" in the portal turns them into lead fields; anything unmapped\n// is kept as a custom field.\n$submission = [\n    'form_id' => 'website-contact',\n    'data' => [\n        'full_name' => 'Jordan Ellis',\n        'phone' => '4155550142',\n        'email' => 'jordan.ellis@example.com',\n        'message' => 'Screen cracked, touch still works. Need a quote for a replacement.',\n        'Preferred contact time' => 'After 6 pm',\n    ],\n];\n\n$ch = curl_init(\"https://{$workspace}.api.bytephase.com/api/integrations/submit\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POSTFIELDS => json_encode($submission),\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$apiKey}\",\n        'Content-Type: application/json',\n        'Accept: application/json',\n        'Idempotency-Key: ' . bin2hex(random_bytes(16)),\n    ],\n]);\n\n$raw = curl_exec($ch);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n\n$body = json_decode($raw, true) ?? [];\n\nif ($status >= 400) {\n    fwrite(STDERR, sprintf(\"Failed (%d) %s: %s\\n\", $status, $body['error']['code'] ?? '', $body['message'] ?? $raw));\n    foreach ($body['error']['details'] ?? [] as $detail) {\n        fwrite(STDERR, \"  {$detail['field']}: {$detail['message']}\\n\");\n    }\n    fwrite(STDERR, 'Request id: ' . ($body['error']['request_id'] ?? 'unknown') . \"\\n\");\n    exit(1);\n}\n\nif ($body['status'] === 'received') {\n    // 202: the form is not configured in the portal yet. The submission is parked and can be\n    // replayed from the portal once it is; do not retry.\n    printf(\"Parked until form \\\"%s\\\" is configured (request %s)\\n\", $submission['form_id'], $body['request_id']);\n} else {\n    printf(\"%s: %s #%d (request %s)\\n\", $body['status'], $body['data']['object'], $body['data']['id'], $body['request_id']);\n}\n"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "# Python 3 with requests (pip install requests). Replace the two placeholders and run: python3 submit_form.py\nimport sys\nimport uuid\n\nimport requests\n\nWORKSPACE = \"your-workspace\"\nAPI_KEY = \"bp_your_api_key\"\n\n# The field names under \"data\" are the form's own. The mapping configured for\n# \"website-contact\" in the portal turns them into lead fields; anything unmapped\n# is kept as a custom field.\nsubmission = {\n    \"form_id\": \"website-contact\",\n    \"data\": {\n        \"full_name\": \"Jordan Ellis\",\n        \"phone\": \"4155550142\",\n        \"email\": \"jordan.ellis@example.com\",\n        \"message\": \"Screen cracked, touch still works. Need a quote for a replacement.\",\n        \"Preferred contact time\": \"After 6 pm\",\n    },\n}\n\nresponse = requests.post(\n    f\"https://{WORKSPACE}.api.bytephase.com/api/integrations/submit\",\n    json=submission,\n    headers={\n        \"Authorization\": f\"Bearer {API_KEY}\",\n        \"Accept\": \"application/json\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    timeout=15,\n)\nbody = response.json()\n\nif not response.ok:\n    error = body.get(\"error\", {})\n    print(f\"Failed ({response.status_code}) {error.get('code', '')}: {body.get('message')}\", file=sys.stderr)\n    for detail in error.get(\"details\", []):\n        print(f\"  {detail['field']}: {detail['message']}\", file=sys.stderr)\n    print(f\"Request id: {response.headers.get('X-Request-Id')}\", file=sys.stderr)\n    sys.exit(1)\n\nif body[\"status\"] == \"received\":\n    # 202: the form is not configured in the portal yet. The submission is parked and can be\n    # replayed from the portal once it is; do not retry.\n    print(f'Parked until form \"{submission[\"form_id\"]}\" is configured (request {body[\"request_id\"]})')\nelse:\n    print(f\"{body['status']}: {body['data']['object']} #{body['data']['id']} (request {body['request_id']})\")\n"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/SubmissionDuplicate"
          },
          "201": {
            "$ref": "#/components/responses/SubmissionCreated"
          },
          "202": {
            "$ref": "#/components/responses/SubmissionReceived"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/integrations/leads": {
      "post": {
        "operationId": "createLead",
        "tags": [
          "Intake"
        ],
        "summary": "Create a lead",
        "description": "Create a sales lead from a flat JSON body. Use this when **you** build the request — a custom website, a\nscript, an n8n workflow — and you want a lead. If a third-party form builder posts to BytePhase directly,\nuse `POST /integrations/ingest/{adapter}` instead; if you want the portal's form configuration to decide\nbetween lead and self check-in, use `POST /integrations/submit`.\n\n**Validation is strict on this route.** `name` is required, and so is one of `email` or `mobile_number`.\nA field that breaks a rule returns `422` and nothing is created. Fields not listed in the schema are kept as\ncustom fields on the lead (up to 20; values cut at 1,000 characters), so a form can pass its own questions\nthrough with no configuration.\n\n**Duplicates.** By default a lead with the same mobile number and country code, or the same email, is not\ncreated again: the existing lead comes back with status `duplicate` and HTTP 200. The policy can be changed\nper form in the portal.\n\n**Idempotency.** Send an `Idempotency-Key` and reuse it on retry; see the header.\n\n**Parking.** If the key belongs to a provider whose forms must be mapped before use (Typeform, JotForm,\nTally.so) and `form_id` names a form that is not configured yet, the submission is parked with `202 received`.\n\nThe lead is assigned to the shop's default reception user and fires the same notifications as a lead created\nin the app.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeadCreate"
              },
              "examples": {
                "Website enquiry": {
                  "$ref": "#/components/examples/LeadCreateRequest"
                },
                "Minimal": {
                  "$ref": "#/components/examples/LeadCreateMinimalRequest"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "label": "cURL",
            "source": "#!/usr/bin/env sh\n# Replace your-workspace and bp_your_api_key. The Idempotency-Key makes a retry safe:\n# resend the same key after a timeout and you get the original lead back, not a second one.\ncurl -sS -w '\\nHTTP %{http_code}\\n' -X POST \\\n  \"https://your-workspace.api.bytephase.com/api/integrations/leads\" \\\n  -H \"Authorization: Bearer bp_your_api_key\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"form_id\": \"website-contact\",\n    \"name\": \"Jordan Ellis\",\n    \"email\": \"jordan.ellis@example.com\",\n    \"mobile_number\": \"4155550142\",\n    \"mobile_country_code\": \"+1\",\n    \"source\": \"Website\",\n    \"comment\": \"Screen cracked, touch still works. Need a quote for a replacement.\",\n    \"device_type\": \"Mobile\",\n    \"device_brand\": \"Apple\",\n    \"device_model\": \"iPhone 15 Pro\",\n    \"preferred_contact_time\": \"After 6 pm\"\n  }'\n"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript",
            "source": "// Node.js 18+ (built-in fetch). Replace the two placeholders and run: node create-lead.js\nconst { randomUUID } = require(\"node:crypto\");\n\nconst WORKSPACE = \"your-workspace\";\nconst API_KEY = \"bp_your_api_key\";\n\nconst lead = {\n  form_id: \"website-contact\",\n  name: \"Jordan Ellis\",\n  email: \"jordan.ellis@example.com\",\n  mobile_number: \"4155550142\",\n  mobile_country_code: \"+1\",\n  source: \"Website\",\n  comment: \"Screen cracked, touch still works. Need a quote for a replacement.\",\n  device_type: \"Mobile\",\n  device_brand: \"Apple\",\n  device_model: \"iPhone 15 Pro\",\n  // Not a lead field, so it is stored as a custom field on the lead.\n  preferred_contact_time: \"After 6 pm\",\n};\n\n// Generate one key per logical submission and reuse it on retry.\nconst idempotencyKey = randomUUID();\n\nasync function main() {\n  const response = await fetch(`https://${WORKSPACE}.api.bytephase.com/api/integrations/leads`, {\n    method: \"POST\",\n    headers: {\n      Authorization: `Bearer ${API_KEY}`,\n      \"Content-Type\": \"application/json\",\n      Accept: \"application/json\",\n      \"Idempotency-Key\": idempotencyKey,\n    },\n    body: JSON.stringify(lead),\n  });\n\n  const body = await response.json();\n\n  if (!response.ok) {\n    console.error(`Failed (${response.status}) ${body.error?.code ?? \"\"}: ${body.message}`);\n    for (const detail of body.error?.details ?? []) {\n      console.error(`  ${detail.field}: ${detail.message}`);\n    }\n    console.error(`Request id: ${response.headers.get(\"X-Request-Id\")}`);\n    process.exit(1);\n  }\n\n  // status is \"completed\" (201, new lead), \"duplicate\" (200, existing lead returned)\n  // or \"received\" (202, parked until the form is configured in the portal; data is null).\n  console.log(`${body.status}: lead #${body.data?.id ?? \"-\"} (request ${body.request_id})`);\n}\n\nmain().catch((error) => {\n  console.error(error.message);\n  process.exit(1);\n});\n"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n// Plain PHP with the curl extension. Replace the two placeholders and run: php create-lead.php\n$workspace = 'your-workspace';\n$apiKey = 'bp_your_api_key';\n\n$lead = [\n    'form_id' => 'website-contact',\n    'name' => 'Jordan Ellis',\n    'email' => 'jordan.ellis@example.com',\n    'mobile_number' => '4155550142',\n    'mobile_country_code' => '+1',\n    'source' => 'Website',\n    'comment' => 'Screen cracked, touch still works. Need a quote for a replacement.',\n    'device_type' => 'Mobile',\n    'device_brand' => 'Apple',\n    'device_model' => 'iPhone 15 Pro',\n    // Not a lead field, so it is stored as a custom field on the lead.\n    'preferred_contact_time' => 'After 6 pm',\n];\n\n// One key per logical submission; reuse it if you retry after a timeout.\n$idempotencyKey = bin2hex(random_bytes(16));\n\n$ch = curl_init(\"https://{$workspace}.api.bytephase.com/api/integrations/leads\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POSTFIELDS => json_encode($lead),\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$apiKey}\",\n        'Content-Type: application/json',\n        'Accept: application/json',\n        \"Idempotency-Key: {$idempotencyKey}\",\n    ],\n]);\n\n$raw = curl_exec($ch);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n\n$body = json_decode($raw, true) ?? [];\n\nif ($status >= 400) {\n    fwrite(STDERR, sprintf(\"Failed (%d) %s: %s\\n\", $status, $body['error']['code'] ?? '', $body['message'] ?? $raw));\n    foreach ($body['error']['details'] ?? [] as $detail) {\n        fwrite(STDERR, \"  {$detail['field']}: {$detail['message']}\\n\");\n    }\n    fwrite(STDERR, 'Request id: ' . ($body['error']['request_id'] ?? 'unknown') . \"\\n\");\n    exit(1);\n}\n\n// status is \"completed\" (201, new lead), \"duplicate\" (200, existing lead returned)\n// or \"received\" (202, parked until the form is configured in the portal; data is null).\nprintf(\"%s: lead #%s (request %s)\\n\", $body['status'], $body['data']['id'] ?? '-', $body['request_id']);\n"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "# Python 3 with requests (pip install requests). Replace the two placeholders and run: python3 create_lead.py\nimport sys\nimport uuid\n\nimport requests\n\nWORKSPACE = \"your-workspace\"\nAPI_KEY = \"bp_your_api_key\"\n\nlead = {\n    \"form_id\": \"website-contact\",\n    \"name\": \"Jordan Ellis\",\n    \"email\": \"jordan.ellis@example.com\",\n    \"mobile_number\": \"4155550142\",\n    \"mobile_country_code\": \"+1\",\n    \"source\": \"Website\",\n    \"comment\": \"Screen cracked, touch still works. Need a quote for a replacement.\",\n    \"device_type\": \"Mobile\",\n    \"device_brand\": \"Apple\",\n    \"device_model\": \"iPhone 15 Pro\",\n    # Not a lead field, so it is stored as a custom field on the lead.\n    \"preferred_contact_time\": \"After 6 pm\",\n}\n\n# One key per logical submission; reuse it if you retry after a timeout.\nidempotency_key = str(uuid.uuid4())\n\nresponse = requests.post(\n    f\"https://{WORKSPACE}.api.bytephase.com/api/integrations/leads\",\n    json=lead,\n    headers={\n        \"Authorization\": f\"Bearer {API_KEY}\",\n        \"Accept\": \"application/json\",\n        \"Idempotency-Key\": idempotency_key,\n    },\n    timeout=15,\n)\nbody = response.json()\n\nif not response.ok:\n    error = body.get(\"error\", {})\n    print(f\"Failed ({response.status_code}) {error.get('code', '')}: {body.get('message')}\", file=sys.stderr)\n    for detail in error.get(\"details\", []):\n        print(f\"  {detail['field']}: {detail['message']}\", file=sys.stderr)\n    print(f\"Request id: {response.headers.get('X-Request-Id')}\", file=sys.stderr)\n    sys.exit(1)\n\n# status is \"completed\" (201, new lead), \"duplicate\" (200, existing lead returned)\n# or \"received\" (202, parked until the form is configured in the portal; data is None).\nlead_id = (body.get(\"data\") or {}).get(\"id\", \"-\")\nprint(f\"{body['status']}: lead #{lead_id} (request {body['request_id']})\")\n"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/SubmissionDuplicate"
          },
          "201": {
            "$ref": "#/components/responses/SubmissionCreated"
          },
          "202": {
            "$ref": "#/components/responses/SubmissionReceived"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/integrations/self-checkin": {
      "post": {
        "operationId": "createSelfCheckIn",
        "tags": [
          "Intake"
        ],
        "summary": "Create a self check-in",
        "description": "Book a device in on a customer's behalf, ahead of their visit or pickup. The shop sees it in its self\ncheck-in queue, is notified, and converts it into a repair ticket when the device arrives. Use this from a\n\"book a repair\" form on your site or from an automation you control.\n\n**Validation is strict on this route.** `name` is required, and so is one of `email` or `mobile_number`.\nAnything else is optional. Fields not listed in the schema are kept as custom fields (up to 20; values cut\nat 1,000 characters).\n\n**Duplicates.** By default every request creates a new check-in — a customer may genuinely book two\ndevices. If the form's duplicate policy in the portal is set to skip, an **open** check-in with the same\nmobile number is returned instead with status `duplicate` and HTTP 200.\n\n**Idempotency.** Send an `Idempotency-Key` and reuse it on retry; see the header.\n\n`device_password` is accepted so the technician can test the device, is stored for staff only, and is never\nreturned by this API.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SelfCheckInCreate"
              },
              "examples": {
                "Laptop with pickup": {
                  "$ref": "#/components/examples/SelfCheckInCreateRequest"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "label": "cURL",
            "source": "#!/usr/bin/env sh\n# Replace your-workspace and bp_your_api_key. Books a laptop in with a pickup request.\ncurl -sS -w '\\nHTTP %{http_code}\\n' -X POST \\\n  \"https://your-workspace.api.bytephase.com/api/integrations/self-checkin\" \\\n  -H \"Authorization: Bearer bp_your_api_key\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"form_id\": \"book-repair\",\n    \"name\": \"Amelia Clarke\",\n    \"email\": \"amelia.clarke@example.com\",\n    \"mobile_number\": \"2079460958\",\n    \"mobile_country_code\": \"+44\",\n    \"device_type\": \"Laptop\",\n    \"device_brand\": \"Apple\",\n    \"device_model\": \"MacBook Air M2\",\n    \"serial_number\": \"C02XK1ABJG5H\",\n    \"accessories\": [\"Charger\", \"Laptop bag\"],\n    \"comment\": \"Does not power on after a liquid spill.\",\n    \"is_recovery\": true,\n    \"is_pickup_booked\": true,\n    \"scheduled_on\": \"2026-09-10T05:00:00Z\",\n    \"address\": {\n      \"address_line\": \"48 Quayside Lane\",\n      \"city\": \"London\",\n      \"state\": \"Greater London\",\n      \"zip_code\": \"SE1 2AA\"\n    }\n  }'\n"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript",
            "source": "// Node.js 18+ (built-in fetch). Replace the two placeholders and run: node create-self-checkin.js\nconst { randomUUID } = require(\"node:crypto\");\n\nconst WORKSPACE = \"your-workspace\";\nconst API_KEY = \"bp_your_api_key\";\n\nconst checkIn = {\n  form_id: \"book-repair\",\n  name: \"Amelia Clarke\",\n  email: \"amelia.clarke@example.com\",\n  mobile_number: \"2079460958\",\n  mobile_country_code: \"+44\",\n  device_type: \"Laptop\",\n  device_brand: \"Apple\",\n  device_model: \"MacBook Air M2\",\n  serial_number: \"C02XK1ABJG5H\",\n  accessories: [\"Charger\", \"Laptop bag\"],\n  comment: \"Does not power on after a liquid spill.\",\n  is_recovery: true,\n  is_pickup_booked: true,\n  scheduled_on: \"2026-09-10T05:00:00Z\",\n  address: {\n    address_line: \"48 Quayside Lane\",\n    city: \"London\",\n    state: \"Greater London\",\n    zip_code: \"SE1 2AA\",\n  },\n};\n\nasync function main() {\n  const response = await fetch(`https://${WORKSPACE}.api.bytephase.com/api/integrations/self-checkin`, {\n    method: \"POST\",\n    headers: {\n      Authorization: `Bearer ${API_KEY}`,\n      \"Content-Type\": \"application/json\",\n      Accept: \"application/json\",\n      // Reuse the same key if you retry after a timeout.\n      \"Idempotency-Key\": randomUUID(),\n    },\n    body: JSON.stringify(checkIn),\n  });\n\n  const body = await response.json();\n\n  if (!response.ok) {\n    console.error(`Failed (${response.status}) ${body.error?.code ?? \"\"}: ${body.message}`);\n    for (const detail of body.error?.details ?? []) {\n      console.error(`  ${detail.field}: ${detail.message}`);\n    }\n    console.error(`Request id: ${response.headers.get(\"X-Request-Id\")}`);\n    process.exit(1);\n  }\n\n  // \"completed\" (201) is the normal outcome; self check-ins are created every time unless the\n  // form's duplicate policy in the portal says otherwise.\n  console.log(`${body.status}: self check-in #${body.data?.id ?? \"-\"} (request ${body.request_id})`);\n}\n\nmain().catch((error) => {\n  console.error(error.message);\n  process.exit(1);\n});\n"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n// Plain PHP with the curl extension. Replace the two placeholders and run: php create-self-checkin.php\n$workspace = 'your-workspace';\n$apiKey = 'bp_your_api_key';\n\n$checkIn = [\n    'form_id' => 'book-repair',\n    'name' => 'Amelia Clarke',\n    'email' => 'amelia.clarke@example.com',\n    'mobile_number' => '2079460958',\n    'mobile_country_code' => '+44',\n    'device_type' => 'Laptop',\n    'device_brand' => 'Apple',\n    'device_model' => 'MacBook Air M2',\n    'serial_number' => 'C02XK1ABJG5H',\n    'accessories' => ['Charger', 'Laptop bag'],\n    'comment' => 'Does not power on after a liquid spill.',\n    'is_recovery' => true,\n    'is_pickup_booked' => true,\n    'scheduled_on' => '2026-09-10T05:00:00Z',\n    'address' => [\n        'address_line' => '48 Quayside Lane',\n        'city' => 'London',\n        'state' => 'Greater London',\n        'zip_code' => 'SE1 2AA',\n    ],\n];\n\n$ch = curl_init(\"https://{$workspace}.api.bytephase.com/api/integrations/self-checkin\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POSTFIELDS => json_encode($checkIn),\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$apiKey}\",\n        'Content-Type: application/json',\n        'Accept: application/json',\n        // Reuse the same key if you retry after a timeout.\n        'Idempotency-Key: ' . bin2hex(random_bytes(16)),\n    ],\n]);\n\n$raw = curl_exec($ch);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n\n$body = json_decode($raw, true) ?? [];\n\nif ($status >= 400) {\n    fwrite(STDERR, sprintf(\"Failed (%d) %s: %s\\n\", $status, $body['error']['code'] ?? '', $body['message'] ?? $raw));\n    foreach ($body['error']['details'] ?? [] as $detail) {\n        fwrite(STDERR, \"  {$detail['field']}: {$detail['message']}\\n\");\n    }\n    fwrite(STDERR, 'Request id: ' . ($body['error']['request_id'] ?? 'unknown') . \"\\n\");\n    exit(1);\n}\n\n// \"completed\" (201) is the normal outcome; self check-ins are created every time unless the\n// form's duplicate policy in the portal says otherwise.\nprintf(\"%s: self check-in #%s (request %s)\\n\", $body['status'], $body['data']['id'] ?? '-', $body['request_id']);\n"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "# Python 3 with requests (pip install requests). Replace the two placeholders and run: python3 create_self_checkin.py\nimport sys\nimport uuid\n\nimport requests\n\nWORKSPACE = \"your-workspace\"\nAPI_KEY = \"bp_your_api_key\"\n\ncheck_in = {\n    \"form_id\": \"book-repair\",\n    \"name\": \"Amelia Clarke\",\n    \"email\": \"amelia.clarke@example.com\",\n    \"mobile_number\": \"2079460958\",\n    \"mobile_country_code\": \"+44\",\n    \"device_type\": \"Laptop\",\n    \"device_brand\": \"Apple\",\n    \"device_model\": \"MacBook Air M2\",\n    \"serial_number\": \"C02XK1ABJG5H\",\n    \"accessories\": [\"Charger\", \"Laptop bag\"],\n    \"comment\": \"Does not power on after a liquid spill.\",\n    \"is_recovery\": True,\n    \"is_pickup_booked\": True,\n    \"scheduled_on\": \"2026-09-10T05:00:00Z\",\n    \"address\": {\n        \"address_line\": \"48 Quayside Lane\",\n        \"city\": \"London\",\n        \"state\": \"Greater London\",\n        \"zip_code\": \"SE1 2AA\",\n    },\n}\n\nresponse = requests.post(\n    f\"https://{WORKSPACE}.api.bytephase.com/api/integrations/self-checkin\",\n    json=check_in,\n    headers={\n        \"Authorization\": f\"Bearer {API_KEY}\",\n        \"Accept\": \"application/json\",\n        # Reuse the same key if you retry after a timeout.\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    timeout=15,\n)\nbody = response.json()\n\nif not response.ok:\n    error = body.get(\"error\", {})\n    print(f\"Failed ({response.status_code}) {error.get('code', '')}: {body.get('message')}\", file=sys.stderr)\n    for detail in error.get(\"details\", []):\n        print(f\"  {detail['field']}: {detail['message']}\", file=sys.stderr)\n    print(f\"Request id: {response.headers.get('X-Request-Id')}\", file=sys.stderr)\n    sys.exit(1)\n\n# \"completed\" (201) is the normal outcome; self check-ins are created every time unless the\n# form's duplicate policy in the portal says otherwise.\ncheck_in_id = (body.get(\"data\") or {}).get(\"id\", \"-\")\nprint(f\"{body['status']}: self check-in #{check_in_id} (request {body['request_id']})\")\n"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/SubmissionDuplicate"
          },
          "201": {
            "$ref": "#/components/responses/SubmissionCreated"
          },
          "202": {
            "$ref": "#/components/responses/SubmissionReceived"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/integrations/ingest/{adapter}": {
      "post": {
        "operationId": "ingestSubmission",
        "tags": [
          "Intake"
        ],
        "summary": "Receive a provider's webhook",
        "description": "Point a third-party form builder or lead source straight at BytePhase. The `{adapter}` names the provider,\nand BytePhase reads that provider's own payload shape — you do not reshape anything. Use it as the webhook\nURL in Typeform, JotForm or Tally.so, as the action of a plain HTML form (`form`), or with `canonical` when\nyou want the `submit` envelope on a URL that also accepts form-encoded bodies.\n\nAccepts `application/json`, `application/x-www-form-urlencoded` and `multipart/form-data`.\n\n**Field mapping.** Typeform, JotForm and Tally.so post their own question names, so their form must be\nmapped in the portal before submissions become records. Until then they are parked with `202 received` —\nnothing is lost; the portal lists them and replays them once the form is configured. `form` and `canonical`\npayloads that already use BytePhase field names need no mapping.\n\n**Idempotency.** `Idempotency-Key` is honoured when sent. When the provider sends none, a key is **derived**:\nfrom the provider's own submission identifier where it has one (Typeform's response token, JotForm's\n`submissionID`, Tally's `submissionId`, IndiaMART's `UNIQUE_QUERY_ID`, Justdial's `leadid`), otherwise\nfrom a hash of the payload plus today's date. So a provider that redelivers a webhook will not usually\ndouble-create, but an identical payload sent on two different days is treated as two submissions — on\npurpose, since a customer legitimately submitting the same form a week apart should become two leads.\n\n**Signatures.** Typeform and Tally.so webhooks are verified against the signing secret set for this key in\nthe portal when one is configured; a mismatch returns `401 invalid_signature`.\n\n**Provider-locked adapters.** `indiamart` and `justdial` accept only a key created for that provider; any\nother key gets `403 integration_not_permitted`. Those providers cannot send headers, so they use the\n`/{token}` form of this URL instead.\n\nValidation and duplicates behave as on `POST /integrations/submit` (lenient).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/AdapterKey"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          },
          {
            "$ref": "#/components/parameters/DestinationQuery"
          },
          {
            "$ref": "#/components/parameters/FormIdQuery"
          },
          {
            "$ref": "#/components/parameters/TypeformSignature"
          },
          {
            "$ref": "#/components/parameters/TallySignature"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The provider's payload, in the provider's shape.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IngestPayload"
              },
              "examples": {
                "form": {
                  "$ref": "#/components/examples/IngestFormPayload"
                },
                "canonical": {
                  "$ref": "#/components/examples/IngestCanonicalPayload"
                },
                "typeform": {
                  "$ref": "#/components/examples/IngestTypeformPayload"
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/IngestPayload"
              },
              "examples": {
                "form": {
                  "$ref": "#/components/examples/IngestFormPayload"
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/IngestPayload"
              },
              "examples": {
                "form": {
                  "$ref": "#/components/examples/IngestFormPayload"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/SubmissionDuplicate"
          },
          "201": {
            "$ref": "#/components/responses/SubmissionCreated"
          },
          "202": {
            "$ref": "#/components/responses/SubmissionReceived"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/integrations/ingest/{adapter}/{token}": {
      "post": {
        "operationId": "ingestSubmissionWithToken",
        "tags": [
          "Intake"
        ],
        "summary": "Receive a provider's webhook (key in the URL)",
        "description": "The same as `POST /integrations/ingest/{adapter}`, with the API key carried as the last path segment.\n\nThis form exists for **IndiaMART**, whose Lead Manager panel can only be given a URL and cannot set a\nheader. Paste `https://{workspace}.api.bytephase.com/api/integrations/ingest/indiamart/{your key}` into the\npanel's push-URL field; IndiaMART posts each enquiry as JSON and it becomes a lead with source `IndiaMART`.\n\nEvery other caller should send the key in a header and use the route without `/{token}`. A URL that\ncontains a key is a secret: rotate the key from the portal if it is ever exposed. A header is still\naccepted here and, when present, is used instead of the path segment.\n\nBehaviour — adapters, idempotency, parking, validation — is identical to the header route.\n",
        "security": [
          {},
          {
            "BearerApiKey": []
          },
          {
            "HeaderApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AdapterKey"
          },
          {
            "$ref": "#/components/parameters/UrlToken"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          },
          {
            "$ref": "#/components/parameters/DestinationQuery"
          },
          {
            "$ref": "#/components/parameters/FormIdQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The provider's payload, in the provider's shape.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IngestPayload"
              },
              "examples": {
                "indiamart": {
                  "$ref": "#/components/examples/IngestIndiaMartPayload"
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/IngestPayload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/SubmissionDuplicate"
          },
          "201": {
            "$ref": "#/components/responses/SubmissionCreated"
          },
          "202": {
            "$ref": "#/components/responses/SubmissionReceived"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "operationId": "ingestJustdialEnquiry",
        "tags": [
          "Intake"
        ],
        "summary": "Receive a Justdial enquiry",
        "description": "**The one side-effecting GET on this API**, and it is limited to `justdial`. Justdial delivers each enquiry\nby calling a URL you give their account manager, with the lead in the query string — there is no POST\noption. Give them `https://{workspace}.api.bytephase.com/api/integrations/ingest/justdial/{your key}`.\n\nEach call creates a lead with source `Justdial` from the `name`, `mobile`, `phone`, `email`, `city`,\n`area`, `pincode`, `date` and `time` parameters. Every other parameter Justdial sends is kept as a custom\nfield prefixed `justdial_`, so nothing is dropped if their format changes.\n\nRedeliveries are recognised by `leadid`, or by a fingerprint of the enquiry for that day when `leadid` is\nabsent, and return the existing lead with status `duplicate`.\n\nBecause this URL creates records when opened, treat it as a secret: do not paste it into chat tools that\nunfurl links, and rotate the key from the portal if it leaks. Only a key created for the Justdial provider\nis accepted.\n",
        "security": [
          {},
          {
            "BearerApiKey": []
          },
          {
            "HeaderApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/JustdialAdapterKey"
          },
          {
            "$ref": "#/components/parameters/UrlToken"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          },
          {
            "name": "leadid",
            "in": "query",
            "required": false,
            "description": "Justdial's identifier for the enquiry. Used to recognise redeliveries.",
            "schema": {
              "type": "string",
              "examples": [
                "JD1A2B3C4D"
              ]
            }
          },
          {
            "name": "prefix",
            "in": "query",
            "required": false,
            "description": "Salutation, prepended to `name`.",
            "schema": {
              "type": "string",
              "examples": [
                "Mr"
              ]
            }
          },
          {
            "name": "name",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "examples": [
                "Mia Nguyen"
              ]
            }
          },
          {
            "name": "mobile",
            "in": "query",
            "required": false,
            "description": "Mobile number, with or without the country dialling prefix; split into number and country code.",
            "schema": {
              "type": "string",
              "examples": [
                "255501234"
              ]
            }
          },
          {
            "name": "phone",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "email",
              "examples": [
                "mia.nguyen@example.com"
              ]
            }
          },
          {
            "name": "city",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "examples": [
                "Sydney"
              ]
            }
          },
          {
            "name": "area",
            "in": "query",
            "required": false,
            "description": "Locality; stored as the address line.",
            "schema": {
              "type": "string",
              "examples": [
                "Surry Hills"
              ]
            }
          },
          {
            "name": "pincode",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "examples": [
                "2010"
              ]
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "The Justdial category the enquiry came from; stored in the lead's comment.",
            "schema": {
              "type": "string",
              "examples": [
                "Mobile Phone Repair & Services"
              ]
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Enquiry date in IST, as Justdial formats it.",
            "schema": {
              "type": "string",
              "examples": [
                "2026-09-09"
              ]
            }
          },
          {
            "name": "time",
            "in": "query",
            "required": false,
            "description": "Enquiry time in IST, as Justdial formats it.",
            "schema": {
              "type": "string",
              "examples": [
                "15:42:10"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/SubmissionDuplicate"
          },
          "201": {
            "$ref": "#/components/responses/SubmissionCreated"
          },
          "202": {
            "$ref": "#/components/responses/SubmissionReceived"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/zapier/ping": {
      "get": {
        "operationId": "zapierPing",
        "tags": [
          "Zapier"
        ],
        "summary": "Test a Zapier connection",
        "description": "The connection test the BytePhase Zapier app runs when a user connects their account. Requires a key whose\nprovider is **Zapier**; any other key gets `403`. Read-only.\n\nCustom integrations should use `GET /integrations/ping`, which accepts any key. This route is documented\nso the Zapier app's behaviour is a matter of record. `status` is the field the app reads; `workspace` and\n`provider` are the same as on `GET /integrations/ping`; `service` and `tenant` are legacy fields that will\nbe removed once the Zapier app is confirmed not to read them.\n",
        "responses": {
          "200": {
            "description": "Connected.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Business": {
                "$ref": "#/components/headers/XBusiness"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ZapierConnectionStatus"
                },
                "examples": {
                  "Connected": {
                    "$ref": "#/components/examples/ZapierPing"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/zapier/triggers/jobs": {
      "get": {
        "operationId": "listRecentRepairs",
        "tags": [
          "Zapier"
        ],
        "summary": "List recent repairs",
        "description": "The \"New repair\" polling trigger. Lists the most recently created repair tickets with their customer, device,\nstatus and payment summary. Use it to react to new work coming in — a Slack message, a row in a sheet, a\nCRM update.\n\nReturns a **bare JSON array**, newest first, of at most `limit` records — no envelope, no cursor. Poll it\non a schedule and de-duplicate on `id`. Requires a key whose provider is **Zapier**.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          }
        ],
        "responses": {
          "200": {
            "description": "The most recent records, newest first. An empty array when there are none.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Business": {
                "$ref": "#/components/headers/XBusiness"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Repair"
                  }
                },
                "examples": {
                  "Recent records": {
                    "$ref": "#/components/examples/RepairList"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/zapier/triggers/customers": {
      "get": {
        "operationId": "listRecentCustomers",
        "tags": [
          "Zapier"
        ],
        "summary": "List recent customers",
        "description": "The \"New customer\" polling trigger. Lists the most recently created customers. Use it to sync new customers\ninto a mailing list or CRM.\n\nReturns a **bare JSON array**, newest first, of at most `limit` records — no envelope, no cursor. Poll it\non a schedule and de-duplicate on `id`. Requires a key whose provider is **Zapier**.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          }
        ],
        "responses": {
          "200": {
            "description": "The most recent records, newest first. An empty array when there are none.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Business": {
                "$ref": "#/components/headers/XBusiness"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Customer"
                  }
                },
                "examples": {
                  "Recent records": {
                    "$ref": "#/components/examples/CustomerList"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/zapier/triggers/leads": {
      "get": {
        "operationId": "listRecentLeads",
        "tags": [
          "Zapier"
        ],
        "summary": "List recent leads",
        "description": "The \"New lead\" polling trigger. Lists the most recently created leads, including those that arrived through\nthe intake routes of this API. Use it to route new enquiries to a sales tool or a follow-up sequence.\n\nReturns a **bare JSON array**, newest first, of at most `limit` records — no envelope, no cursor. Poll it\non a schedule and de-duplicate on `id`. Requires a key whose provider is **Zapier**.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          }
        ],
        "responses": {
          "200": {
            "description": "The most recent records, newest first. An empty array when there are none.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Business": {
                "$ref": "#/components/headers/XBusiness"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ZapierLead"
                  }
                },
                "examples": {
                  "Recent records": {
                    "$ref": "#/components/examples/LeadList"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/zapier/triggers/payments": {
      "get": {
        "operationId": "listRecentPayments",
        "tags": [
          "Zapier"
        ],
        "summary": "List recent payments",
        "description": "The \"New payment\" polling trigger. Lists the most recently recorded payments with the customer and the\nrecord they settle. Use it for bookkeeping exports or payment receipts. Amounts are plain numbers in the\nworkspace currency.\n\nReturns a **bare JSON array**, newest first, of at most `limit` records — no envelope, no cursor. Poll it\non a schedule and de-duplicate on `id`. Requires a key whose provider is **Zapier**.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          }
        ],
        "responses": {
          "200": {
            "description": "The most recent records, newest first. An empty array when there are none.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Business": {
                "$ref": "#/components/headers/XBusiness"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Payment"
                  }
                },
                "examples": {
                  "Recent records": {
                    "$ref": "#/components/examples/PaymentList"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/zapier/triggers/job-status-changes": {
      "get": {
        "operationId": "listRecentRepairStatusChanges",
        "tags": [
          "Zapier"
        ],
        "summary": "List recent repair status changes",
        "description": "The \"Repair status changed\" polling trigger. Lists the most recent entries in repair status history — one\nper change, with the status moved to, who changed it and the customer. Use it to notify customers or update\na tracking board when a repair moves stage.\n\nReturns a **bare JSON array**, newest first, of at most `limit` records — no envelope, no cursor. Poll it\non a schedule and de-duplicate on `id`. Requires a key whose provider is **Zapier**.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          }
        ],
        "responses": {
          "200": {
            "description": "The most recent records, newest first. An empty array when there are none.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Business": {
                "$ref": "#/components/headers/XBusiness"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RepairStatusChange"
                  }
                },
                "examples": {
                  "Recent records": {
                    "$ref": "#/components/examples/RepairStatusChangeList"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/zapier/triggers/outsourced-jobs": {
      "get": {
        "operationId": "listRecentOutsourcedRepairs",
        "tags": [
          "Zapier"
        ],
        "summary": "List recent outsourced repairs",
        "description": "The \"New outsourced repair\" polling trigger. Lists the most recent repairs (or parts of repairs) handed to an\noutside vendor, with the vendor, price and device. Use it to track vendor work or trigger a purchase order.\n\nReturns a **bare JSON array**, newest first, of at most `limit` records — no envelope, no cursor. Poll it\non a schedule and de-duplicate on `id`. Requires a key whose provider is **Zapier**.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          }
        ],
        "responses": {
          "200": {
            "description": "The most recent records, newest first. An empty array when there are none.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Business": {
                "$ref": "#/components/headers/XBusiness"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OutsourcedRepair"
                  }
                },
                "examples": {
                  "Recent records": {
                    "$ref": "#/components/examples/OutsourcedRepairList"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/zapier/search/customers": {
      "get": {
        "operationId": "searchCustomers",
        "tags": [
          "Zapier"
        ],
        "summary": "Find customers",
        "description": "The \"Find customer\" search used by the Zapier app before an action, to look up an existing customer by\nanything you know about them. `search_term` is matched anywhere in the name, email or mobile number.\n\nReturns a **bare JSON array** of up to 25 matches, newest first — an empty array, not an error, when nothing\nmatches. Requires a key whose provider is **Zapier**.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/SearchTerm"
          },
          {
            "$ref": "#/components/parameters/XBusiness"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching customers, newest first, at most 25.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Business": {
                "$ref": "#/components/headers/XBusiness"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "maxItems": 25,
                  "items": {
                    "$ref": "#/components/schemas/Customer"
                  }
                },
                "examples": {
                  "Matches": {
                    "$ref": "#/components/examples/CustomerSearchResult"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "bp_ prefixed API key",
        "description": "Preferred. `Authorization: Bearer bp_…` with an API key created in the BytePhase portal\nunder Settings → Integrations.\n"
      },
      "HeaderApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Alternative for tools that cannot set an `Authorization` header. Same key, same behaviour.\nIf both headers are present, `X-API-Key` wins.\n"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "The one error shape on this API. `error.code` is the field to branch on. `message` at the\ntop level mirrors `error.message` for older clients that only read that field; prefer\n`error.message`.\n",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "request_id",
              "documentation_url"
            ],
            "properties": {
              "code": {
                "$ref": "#/components/schemas/ErrorCode"
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation. Safe to display; never contains internals.",
                "examples": [
                  "The API key is invalid, inactive or expired."
                ]
              },
              "details": {
                "type": "array",
                "description": "Present on `validation_failed` only — one entry per failed rule.",
                "items": {
                  "$ref": "#/components/schemas/ErrorDetail"
                }
              },
              "request_id": {
                "type": "string",
                "description": "Matches the `X-Request-Id` response header. Quote it to support.",
                "examples": [
                  "01J9Z5X8K3Q4R7T2V9W1Y3A5B7"
                ]
              },
              "documentation_url": {
                "type": "string",
                "format": "uri",
                "description": "Link to the explanation of this code on the developer portal.",
                "examples": [
                  "https://developers.bytephase.com/errors#credential_invalid"
                ]
              }
            }
          },
          "message": {
            "type": "string",
            "deprecated": true,
            "description": "Same text as `error.message`. Kept for clients that predate the `error` object.",
            "examples": [
              "The API key is invalid, inactive or expired."
            ]
          }
        }
      },
      "ErrorCode": {
        "type": "string",
        "description": "Stable, machine-readable error code. Branch on this, never on `message`. New codes may be\nadded; existing codes will not change meaning.\n\n| Code | HTTP | Raised when |\n|---|---|---|\n| `malformed_request` | 400 | The body could not be parsed. |\n| `credential_missing` | 401 | No API key was presented. |\n| `credential_invalid` | 401 | The key is unknown, disabled or expired. |\n| `invalid_signature` | 401 | A provider signature (Typeform, Tally.so) did not verify. |\n| `integration_not_permitted` | 403 | The key belongs to a provider that may not use this route or adapter. |\n| `business_not_active` | 403 | The business named by `business_id` / `X-Business` exists but is not active. |\n| `unsupported_adapter` | 404 | The `{adapter}` in the URL is not one of the supported keys. |\n| `not_found` | 404 | The requested resource does not exist. |\n| `business_not_found` | 404 | No business matches `business_id` / `X-Business`. |\n| `idempotency_conflict` | 409 | Reserved. Reusing a key with a different body currently replays the original response instead. |\n| `validation_failed` | 422 | One or more fields failed validation; see `details`. |\n| `unreadable_submission` | 422 | The adapter could not make sense of the provider payload. |\n| `workspace_not_ready` | 423 | The workspace is still being prepared. Retry after a short delay. |\n| `rate_limit_exceeded` | 429 | More than 60 requests in a minute for this key. |\n| `internal_error` | 500 | Our fault. Quote `request_id` to support. |\n",
        "enum": [
          "malformed_request",
          "credential_missing",
          "credential_invalid",
          "invalid_signature",
          "integration_not_permitted",
          "business_not_active",
          "unsupported_adapter",
          "not_found",
          "business_not_found",
          "idempotency_conflict",
          "validation_failed",
          "unreadable_submission",
          "workspace_not_ready",
          "rate_limit_exceeded",
          "internal_error"
        ]
      },
      "ErrorDetail": {
        "type": "object",
        "description": "One failed field. A field can appear more than once when it breaks more than one rule.",
        "required": [
          "field",
          "message"
        ],
        "properties": {
          "field": {
            "type": "string",
            "description": "Dotted path of the offending field, for example `mobile_number` or `data.email`.",
            "examples": [
              "mobile_number"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable reason, safe to show to an end user.",
            "examples": [
              "The mobile number field is required when email is not present."
            ]
          }
        }
      },
      "ValidationError": {
        "description": "A `422` error. For `validation_failed` there is one `details` entry per failed rule; `unreadable_submission` carries none.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Error"
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "enum": [
                      "validation_failed",
                      "unreadable_submission"
                    ]
                  },
                  "message": {
                    "type": "string"
                  },
                  "details": {
                    "type": "array",
                    "description": "Present on `validation_failed`, one entry per failed rule.",
                    "items": {
                      "$ref": "#/components/schemas/ErrorDetail"
                    }
                  },
                  "request_id": {
                    "type": "string"
                  },
                  "documentation_url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        ]
      },
      "Address": {
        "type": "object",
        "description": "Postal address of the person submitting. Every part is optional; an address with no filled part is ignored.",
        "properties": {
          "address_line": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "examples": [
              "12 Harbor Street"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "San Francisco"
            ]
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "CA"
            ]
          },
          "zip_code": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "description": "Postal / ZIP code.",
            "examples": [
              "94105"
            ]
          }
        }
      },
      "CustomField": {
        "type": "object",
        "description": "One custom field as stored on the record. Custom fields hold anything the shop has added to its own\nforms, plus any request field BytePhase did not recognise.\n",
        "required": [
          "label",
          "field_type",
          "field_value"
        ],
        "properties": {
          "label": {
            "type": "string",
            "maxLength": 191,
            "examples": [
              "Preferred contact time"
            ]
          },
          "field_type": {
            "type": "string",
            "enum": [
              "Text",
              "Number",
              "Dropdown"
            ],
            "default": "Text"
          },
          "field_value": {
            "type": "string",
            "maxLength": 1000,
            "examples": [
              "After 6 pm"
            ]
          },
          "select_box_items": {
            "type": [
              "array",
              "null"
            ],
            "description": "The options a `Dropdown` field offers. `null` for other types.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CustomFieldsInput": {
        "description": "Custom fields as sent in a request. Two shapes are accepted and both are stored the same way:\n\n1. A flat map of `label → value` — the simplest form and what most website forms send.\n2. A list of structured entries with `label`, `field_type`, `field_value` and, for dropdowns, `select_box_items`.\n\nValues are strings and are cut at 1,000 characters. Unknown top-level request fields are folded into custom\nfields too, unless a custom field with the same label was sent explicitly.\n",
        "oneOf": [
          {
            "type": "object",
            "title": "Flat map",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean",
                "null"
              ]
            },
            "examples": [
              {
                "Preferred contact time": "After 6 pm",
                "Warranty": "Yes"
              }
            ]
          },
          {
            "type": "array",
            "title": "Structured entries",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string",
                  "maxLength": 191
                },
                "field_name": {
                  "type": "string",
                  "maxLength": 191,
                  "description": "Accepted as an alias of `label`."
                },
                "field_type": {
                  "type": "string",
                  "enum": [
                    "Text",
                    "Number",
                    "Dropdown"
                  ],
                  "default": "Text"
                },
                "field_value": {
                  "type": "string",
                  "maxLength": 1000
                },
                "select_box_items": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            },
            "examples": [
              [
                {
                  "label": "Preferred contact time",
                  "field_type": "Dropdown",
                  "field_value": "After 6 pm",
                  "select_box_items": [
                    "Morning",
                    "Afternoon",
                    "After 6 pm"
                  ]
                }
              ]
            ]
          }
        ]
      },
      "Provider": {
        "type": "string",
        "description": "The provider a key was created for in the portal. Intake routes accept any of these; the Zapier read routes\naccept only `Zapier`. More providers may be added.\n",
        "examples": [
          "WordPress",
          "Website",
          "REST API",
          "Zapier",
          "n8n",
          "Typeform",
          "JotForm",
          "Tally.so",
          "Meta Lead Ads",
          "IndiaMART",
          "Justdial"
        ]
      },
      "Timestamp": {
        "type": "string",
        "format": "date-time",
        "description": "UTC instant in ISO 8601 with microseconds and a `Z` suffix. Convert to the workspace timezone yourself.",
        "examples": [
          "2026-09-09T09:14:32.000000Z"
        ]
      },
      "ConnectionStatus": {
        "type": "object",
        "description": "Confirms that the key, the host and the workspace line up.",
        "required": [
          "status",
          "workspace",
          "provider"
        ],
        "properties": {
          "status": {
            "type": "string",
            "const": "connected"
          },
          "workspace": {
            "type": "string",
            "description": "The workspace's display name — a readable label for the connection.",
            "examples": [
              "Harbor Repair Co."
            ]
          },
          "provider": {
            "$ref": "#/components/schemas/Provider"
          }
        }
      },
      "ZapierConnectionStatus": {
        "type": "object",
        "description": "Connection test used by the BytePhase Zapier app. `status` is the field the app reads; `workspace` and\n`provider` match `GET /integrations/ping`. `service` and `tenant` are legacy fields the current Zapier app\nstill receives; do not build on them.\n",
        "required": [
          "status",
          "workspace",
          "provider"
        ],
        "properties": {
          "status": {
            "type": "string",
            "const": "connected"
          },
          "workspace": {
            "type": "string",
            "description": "The workspace's display name — a readable label for the connection.",
            "examples": [
              "Harbor Repair Co."
            ]
          },
          "provider": {
            "$ref": "#/components/schemas/Provider"
          },
          "service": {
            "type": "string",
            "deprecated": true,
            "description": "Legacy alias of `provider`, always `Zapier` on this route. Kept for the existing Zapier app.",
            "examples": [
              "Zapier"
            ]
          },
          "tenant": {
            "type": "string",
            "deprecated": true,
            "description": "Legacy field kept for the existing Zapier app. Its value is not meaningful to integrators and will be removed."
          }
        }
      },
      "SubmissionStatus": {
        "type": "string",
        "description": "Outcome of an intake request.\n\n| Value | HTTP | Meaning |\n|---|---|---|\n| `completed` | 201 | A new record was created; `data` is the record. |\n| `duplicate` | 200 | Nothing new was created. Either the `Idempotency-Key` was seen before (the original outcome is returned, including its original `request_id`), or the form's duplicate policy matched an existing record and `data` is that record. |\n| `received` | 202 | The submission was accepted and **parked**: the form is not configured yet in the portal, or its field mapping is still a draft. Nothing was created; `data` is `null`. The submission is kept with its raw payload and can be replayed from the portal once the form is set up. Do not retry — it will not change the outcome. |\n",
        "enum": [
          "completed",
          "duplicate",
          "received"
        ]
      },
      "SubmissionResult": {
        "type": "object",
        "description": "The one envelope every intake route returns.",
        "required": [
          "status",
          "request_id",
          "data"
        ],
        "properties": {
          "status": {
            "$ref": "#/components/schemas/SubmissionStatus"
          },
          "request_id": {
            "type": "string",
            "description": "Identifier of this submission, also sent as the `X-Request-Id` header. On an idempotent replay this is the\nidentifier of the **original** submission, so a retrying client converges on one answer.\n",
            "examples": [
              "01J9Z5X8K3Q4R7T2V9W1Y3A5B7"
            ]
          },
          "data": {
            "description": "The created (or matched) record — a lead or a self check-in — or `null` when `status` is `received`.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/Lead"
              },
              {
                "$ref": "#/components/schemas/SelfCheckIn"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "IntakeContext": {
        "type": "object",
        "description": "Routing fields common to every intake request. Both are optional.",
        "properties": {
          "business_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The business (branch) this submission belongs to, as shown in the portal. Omit to use the workspace's\ndefault business. The `X-Business` header is an alternative and takes precedence when both are sent.\nUnknown → `404 business_not_found`; inactive → `403 business_not_active`.\n",
            "examples": [
              1
            ]
          },
          "form_id": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 191,
            "description": "Your own identifier for the form this came from, matching a form configured for this key in the portal.\nIt selects that form's field mapping and duplicate policy. May be omitted when the key has exactly one form\nconfigured, or none.\n",
            "examples": [
              "website-contact"
            ]
          }
        }
      },
      "LeadFields": {
        "type": "object",
        "title": "Lead fields",
        "description": "Fields of a lead. `name` is required, and so is **one of** `email` or `mobile_number`.\n\nAnything else you send that is not listed here is kept as a custom field (up to 20 extra fields, values cut at\n1,000 characters), so a form can pass its own questions through without configuration.\n",
        "required": [
          "name"
        ],
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 60,
            "description": "Person or company name.",
            "examples": [
              "Jordan Ellis"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "maxLength": 255,
            "description": "Required when `mobile_number` is absent.",
            "examples": [
              "jordan.ellis@example.com"
            ]
          },
          "mobile_number": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "description": "National number without the country code. Required when `email` is absent.",
            "examples": [
              "4155550142"
            ]
          },
          "mobile_country_code": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10,
            "description": "Dialling code with a leading `+`.",
            "examples": [
              "+1"
            ]
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "description": "An alternative landline or second number.",
            "examples": [
              "4155550199"
            ]
          },
          "contact_person_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60,
            "description": "Contact person when `name` is a company.",
            "examples": [
              "Lucas Moreau"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "description": "Where the lead came from, by name. Matched against the shop's configured lead sources; an unknown name is not created and the lead is stored without a source.",
            "examples": [
              "Website"
            ]
          },
          "next_follow_up": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the shop should follow up. Any parseable date; treated as UTC.",
            "examples": [
              "2026-09-12T04:30:00Z"
            ]
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free text — the enquiry itself.",
            "examples": [
              "Screen cracked, touch still works. Need a quote for a replacement."
            ]
          },
          "device_type": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "description": "Matched by name against the shop's device types; kept as free text when there is no match.",
            "examples": [
              "Mobile"
            ]
          },
          "device_brand": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "Apple"
            ]
          },
          "device_model": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "examples": [
              "iPhone 15 Pro"
            ]
          },
          "custom_fields": {
            "$ref": "#/components/schemas/CustomFieldsInput"
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "LeadCreate": {
        "title": "Create lead request",
        "description": "Body of `POST /integrations/leads` — routing fields plus the lead itself, flat.",
        "allOf": [
          {
            "$ref": "#/components/schemas/IntakeContext"
          },
          {
            "$ref": "#/components/schemas/LeadFields"
          }
        ]
      },
      "Lead": {
        "type": "object",
        "title": "Lead",
        "description": "A lead as BytePhase stores it. Every field is always present; unset values are `null`.",
        "required": [
          "id",
          "object",
          "name",
          "email",
          "mobile_number",
          "mobile_country_code",
          "phone_number",
          "contact_person_name",
          "status",
          "status_stage",
          "source",
          "assigned_to",
          "next_follow_up",
          "comment",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Identifier of the lead in this workspace. The same value appears in the Zapier lead list.",
            "examples": [
              10421
            ]
          },
          "object": {
            "type": "string",
            "const": "lead"
          },
          "name": {
            "type": "string",
            "examples": [
              "Jordan Ellis"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "jordan.ellis@example.com"
            ]
          },
          "mobile_number": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "4155550142"
            ]
          },
          "mobile_country_code": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "+1"
            ]
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_person_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "The shop's own label for the lead's current status.",
            "examples": [
              "New"
            ]
          },
          "status_stage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stable stage behind `status`. Branch on this, not on `status`.",
            "enum": [
              "open",
              "in_progress",
              "won",
              "lost",
              null
            ],
            "examples": [
              "open"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the lead source, when one matched.",
            "examples": [
              "Website"
            ]
          },
          "assigned_to": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display name of the staff member the lead was assigned to.",
            "examples": [
              "Alex Morgan"
            ]
          },
          "next_follow_up": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "SelfCheckInFields": {
        "type": "object",
        "title": "Self check-in fields",
        "description": "Fields of a self check-in. `name` is required, and so is **one of** `email` or `mobile_number`.\n\nAnything else you send that is not listed here is kept as a custom field (up to 20 extra fields, values cut at\n1,000 characters).\n",
        "required": [
          "name"
        ],
        "additionalProperties": true,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255,
            "examples": [
              "Amelia Clarke"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "maxLength": 255,
            "description": "Required when `mobile_number` is absent.",
            "examples": [
              "amelia.clarke@example.com"
            ]
          },
          "mobile_number": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "description": "National number without the country code. Required when `email` is absent.",
            "examples": [
              "2079460958"
            ]
          },
          "mobile_country_code": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10,
            "examples": [
              "+44"
            ]
          },
          "device_type": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255,
            "description": "Matched by name against the shop's device types; kept as free text when there is no match.",
            "examples": [
              "Laptop"
            ]
          },
          "device_brand": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255,
            "examples": [
              "Apple"
            ]
          },
          "device_model": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255,
            "examples": [
              "MacBook Air M2"
            ]
          },
          "serial_number": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255,
            "examples": [
              "C02XK1ABJG5H"
            ]
          },
          "serial_number_2": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 191,
            "description": "A second identifier, for example an IMEI on a dual-SIM phone."
          },
          "device_password": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 191,
            "writeOnly": true,
            "description": "Lock-screen password or PIN the customer chooses to share so the technician can test the device. Stored for staff only; never returned by this API."
          },
          "accessories": {
            "description": "Items handed in with the device. Either a list or a comma-separated string.",
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "string"
              }
            ],
            "examples": [
              [
                "Charger",
                "Laptop bag"
              ]
            ]
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "description": "The fault as the customer describes it.",
            "examples": [
              "Does not power on after a liquid spill."
            ]
          },
          "is_recovery": {
            "type": "boolean",
            "default": false,
            "description": "The customer wants data recovery rather than (or as well as) a repair."
          },
          "is_pickup_booked": {
            "type": "boolean",
            "default": false,
            "description": "The customer wants the device collected from their address."
          },
          "scheduled_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the customer will visit or wants the pickup. Any parseable date; treated as UTC.",
            "examples": [
              "2026-09-10T05:00:00Z"
            ]
          },
          "custom_fields": {
            "$ref": "#/components/schemas/CustomFieldsInput"
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "SelfCheckInCreate": {
        "title": "Create self check-in request",
        "description": "Body of `POST /integrations/self-checkin` — routing fields plus the check-in itself, flat.",
        "allOf": [
          {
            "$ref": "#/components/schemas/IntakeContext"
          },
          {
            "$ref": "#/components/schemas/SelfCheckInFields"
          }
        ]
      },
      "SelfCheckIn": {
        "type": "object",
        "title": "Self check-in",
        "description": "A self check-in as BytePhase stores it. Every field is always present; unset values are `null`. `device_password` is never returned.",
        "required": [
          "id",
          "object",
          "name",
          "email",
          "mobile_number",
          "mobile_country_code",
          "device_type",
          "device_brand",
          "device_model",
          "serial_number",
          "serial_number_2",
          "accessories",
          "comment",
          "is_recovery",
          "is_pickup_booked",
          "scheduled_on",
          "status",
          "custom_fields",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Identifier of the self check-in in this workspace.",
            "examples": [
              3187
            ]
          },
          "object": {
            "type": "string",
            "const": "self_checkin"
          },
          "name": {
            "type": "string",
            "examples": [
              "Amelia Clarke"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Empty string when no email was given.",
            "examples": [
              "amelia.clarke@example.com"
            ]
          },
          "mobile_number": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2079460958"
            ]
          },
          "mobile_country_code": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "+44"
            ]
          },
          "device_type": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Laptop"
            ]
          },
          "device_brand": {
            "type": [
              "string",
              "null"
            ],
            "description": "The matched brand, or the free text that was sent when nothing matched.",
            "examples": [
              "Apple"
            ]
          },
          "device_model": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MacBook Air M2"
            ]
          },
          "serial_number": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "C02XK1ABJG5H"
            ]
          },
          "serial_number_2": {
            "type": [
              "string",
              "null"
            ]
          },
          "accessories": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "Charger",
                "Laptop bag"
              ]
            ]
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_recovery": {
            "type": "boolean"
          },
          "is_pickup_booked": {
            "type": "boolean"
          },
          "scheduled_on": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string",
            "description": "Where the check-in is in the shop's workflow. A freshly created one is always `Open`.",
            "enum": [
              "Open",
              "Contacted",
              "Cancelled",
              "Pickup Booked",
              "Pickup Completed",
              "Approved",
              "Converted"
            ],
            "examples": [
              "Open"
            ]
          },
          "custom_fields": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/CustomField"
            }
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "SubmitRequest": {
        "type": "object",
        "title": "Canonical submission envelope",
        "description": "One envelope for any form. `data` carries the form's fields under whatever names the form uses; the portal's\nconfiguration for `form_id` maps them onto the lead or self check-in fields and decides the destination.\n",
        "required": [
          "data"
        ],
        "properties": {
          "business_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The business (branch) this submission belongs to. Omit for the workspace's default business; `X-Business` header takes precedence. Unknown → `404 business_not_found`; inactive → `403 business_not_active`.",
            "examples": [
              1
            ]
          },
          "form_id": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 191,
            "description": "Your identifier for the form, matching one configured for this key in the portal. Optional when the key has exactly one form.",
            "examples": [
              "website-contact"
            ]
          },
          "destination": {
            "description": "Force the destination instead of relying on the form configuration. May also be sent as a `?destination=`\nquery parameter. When omitted and no configured form decides it, the submission is parked (`received`).\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/Destination"
              },
              {
                "type": "null"
              }
            ]
          },
          "data": {
            "type": "object",
            "description": "The submitted fields. After the form's field mapping is applied they must satisfy the destination's\nrules: `name`, plus one of `email` or `mobile_number`. Everything unrecognised is kept as a custom field.\nEmpty strings are dropped, values are trimmed, `yes`/`no`/`on`/`off` become booleans, over-long values are\ncut to the field's limit, and an optional field that still fails validation is moved to custom fields\nrather than rejecting the submission.\n",
            "anyOf": [
              {
                "$ref": "#/components/schemas/LeadFields"
              },
              {
                "$ref": "#/components/schemas/SelfCheckInFields"
              },
              {
                "type": "object",
                "title": "The form's own field names",
                "description": "Any field names, translated by the mapping configured for `form_id` in the portal before the rules above apply.",
                "additionalProperties": true
              }
            ]
          }
        }
      },
      "Destination": {
        "type": "string",
        "description": "What a submission becomes.\n\n- `lead` — a sales lead for the shop to follow up.\n- `self_checkin` — a customer checking a device in ahead of a visit; the shop converts it into a repair ticket.\n",
        "enum": [
          "lead",
          "self_checkin"
        ]
      },
      "AdapterKey": {
        "type": "string",
        "description": "Which provider's payload shape the ingest route should read.\n\n| Adapter | Expects | Idempotency key derived from |\n|---|---|---|\n| `canonical` | The same `{form_id, destination, data}` envelope as `POST /integrations/submit`. | Body hash + date |\n| `form` | Any flat form post — JSON, `application/x-www-form-urlencoded` or `multipart/form-data`. Every field becomes a data field; `form_id` and `destination` may ride along in the body or the query string. | Body hash + date |\n| `typeform` | Typeform's webhook payload (`form_response`). Answers are keyed by the question's `ref`, then its title. Hidden fields are included. | The response `token` |\n| `jotform` | JotForm's webhook post (`rawRequest`, `formID`, `submissionID`). Question prefixes such as `q3_` are stripped. | `submissionID` |\n| `tally-so` | Tally's webhook payload. Fields are keyed by label; choice ids are resolved to their text. | `submissionId` / `responseId` |\n| `indiamart` | IndiaMART Lead Manager's push payload (`RESPONSE`). Requires an IndiaMART key. Always a lead. | `UNIQUE_QUERY_ID` |\n| `justdial` | Justdial's lead delivery as GET query parameters. Requires a Justdial key. Always a lead. | `leadid`, else a fingerprint of the enquiry + date |\n\nTypeform, JotForm and Tally.so submissions post the form builder's own field names, so their form must be\nmapped in the portal first; until then submissions are parked (`received`).\n",
        "enum": [
          "canonical",
          "form",
          "typeform",
          "jotform",
          "tally-so",
          "indiamart",
          "justdial"
        ]
      },
      "IngestPayload": {
        "title": "Provider payload",
        "description": "Whatever the provider named by `{adapter}` sends. The shape is the provider's, not BytePhase's; see the\nexamples for each. For `canonical` this is the same envelope as `POST /integrations/submit`.\n",
        "type": "object",
        "additionalProperties": true
      },
      "CustomFieldDefinition": {
        "type": "object",
        "title": "Custom field definition",
        "description": "One custom field the shop has added to one of its forms, as you should render it on your own form.",
        "required": [
          "field_name",
          "field_type",
          "placeholder",
          "is_field_required",
          "select_box_items"
        ],
        "properties": {
          "field_name": {
            "type": "string",
            "description": "The field's label. Send a value back under exactly this name (as a top-level intake field or a `custom_fields` key) and it lands in this custom field.",
            "examples": [
              "Preferred contact time"
            ]
          },
          "field_type": {
            "type": "string",
            "description": "How the shop renders it. Anything other than `Dropdown` is free text or a number.",
            "enum": [
              "Text",
              "Number",
              "Dropdown"
            ],
            "examples": [
              "Dropdown"
            ]
          },
          "placeholder": {
            "type": [
              "string",
              "null"
            ],
            "description": "Hint text the shop shows in the empty field.",
            "examples": [
              "When should we call?"
            ]
          },
          "is_field_required": {
            "type": "boolean",
            "description": "Whether the shop marks the field as required on its own form. The API does not enforce it; enforce it on yours if you want the same behaviour."
          },
          "select_box_items": {
            "type": "array",
            "description": "The options of a `Dropdown` field. Empty for other types.",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "Morning",
                "Afternoon",
                "After 6 pm"
              ]
            ]
          }
        }
      },
      "CustomFieldFormTypes": {
        "type": "object",
        "title": "Form types with custom fields",
        "description": "The shop's forms that have at least one custom field configured. Returned when no `form_type` is given.",
        "required": [
          "form_types"
        ],
        "properties": {
          "form_types": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "form_type",
                "field_count"
              ],
              "properties": {
                "form_type": {
                  "type": "string",
                  "description": "Name of the form as the shop knows it. Pass it back as `?form_type=` to get its fields.",
                  "examples": [
                    "Lead"
                  ]
                },
                "field_count": {
                  "type": "integer",
                  "minimum": 1,
                  "examples": [
                    2
                  ]
                }
              }
            }
          }
        }
      },
      "CustomFieldDefinitions": {
        "type": "object",
        "title": "Custom fields of one form",
        "description": "The custom fields configured on one of the shop's forms. Returned when `form_type` is given. An unknown or unconfigured `form_type` returns an empty `fields` list, not an error.",
        "required": [
          "form_type",
          "fields"
        ],
        "properties": {
          "form_type": {
            "type": "string",
            "description": "The `form_type` you asked for, echoed back.",
            "examples": [
              "Lead"
            ]
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomFieldDefinition"
            }
          }
        }
      },
      "Repair": {
        "type": "object",
        "title": "Repair",
        "description": "A repair ticket as the Zapier \"New repair\" trigger sees it. Every field is always present; unset values are `null`.",
        "required": [
          "id",
          "job_number",
          "status",
          "status_stage",
          "priority",
          "customer_name",
          "customer_mobile",
          "customer_email",
          "device_type",
          "device_brand",
          "device_model",
          "serial_number",
          "total_amount",
          "payment_status",
          "payment_received",
          "assigned_to",
          "estimated_delivery",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Identifier of the repair. Referenced by `job_id` on status changes and outsourced repairs.",
            "examples": [
              48213
            ]
          },
          "job_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The ticket number printed on the job sheet, in the shop's own numbering.",
            "examples": [
              "JS-2026-0917"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "The shop's own label for the repair's current status.",
            "examples": [
              "In Progress"
            ]
          },
          "status_stage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stable stage behind `status`. Branch on this, not on `status`.",
            "enum": [
              "Open",
              "Closed",
              "Rejected",
              "Returned",
              null
            ],
            "examples": [
              "Open"
            ]
          },
          "priority": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "High"
            ]
          },
          "customer_name": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Lucas Moreau"
            ]
          },
          "customer_mobile": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "170189900"
            ]
          },
          "customer_email": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "lucas.moreau@example.com"
            ]
          },
          "device_type": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Mobile"
            ]
          },
          "device_brand": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Samsung"
            ]
          },
          "device_model": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Galaxy S24"
            ]
          },
          "serial_number": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "R5CX2K7L9MN"
            ]
          },
          "total_amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Total billed for the repair, in the workspace currency, as a plain number.",
            "examples": [
              320
            ]
          },
          "payment_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "Paid",
              "Unpaid",
              "Partially Paid",
              "Free",
              "No Due",
              "Refund",
              "Over Due",
              "Over Paid",
              "Partial Paid Over Due",
              null
            ],
            "examples": [
              "Partially Paid"
            ]
          },
          "payment_received": {
            "type": [
              "number",
              "null"
            ],
            "description": "Amount collected so far, in the workspace currency.",
            "examples": [
              149
            ]
          },
          "assigned_to": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display name of the technician assigned.",
            "examples": [
              "Sofia Rossi"
            ]
          },
          "estimated_delivery": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "Customer": {
        "type": "object",
        "title": "Customer",
        "description": "A customer as the Zapier \"New customer\" trigger and \"Find customer\" search see it.",
        "required": [
          "id",
          "name",
          "email",
          "mobile_number",
          "mobile_country_code",
          "phone_number",
          "gst_number",
          "source",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "examples": [
              882
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Lucas Moreau"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "lucas.moreau@example.com"
            ]
          },
          "mobile_number": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "170189900"
            ]
          },
          "mobile_country_code": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "+33"
            ]
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "gst_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The customer's GSTIN, for business customers in India.",
            "examples": [
              "27ABCDE1234F1Z5"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "How the customer found the shop, by name.",
            "examples": [
              "Walk-in"
            ]
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "ZapierLead": {
        "type": "object",
        "title": "Lead (Zapier)",
        "description": "A lead as the Zapier \"New lead\" trigger sees it. Same fields as the intake `Lead`, without the `object` marker.",
        "required": [
          "id",
          "name",
          "email",
          "mobile_number",
          "mobile_country_code",
          "phone_number",
          "contact_person_name",
          "status",
          "status_stage",
          "source",
          "assigned_to",
          "next_follow_up",
          "comment",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "examples": [
              10421
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Jordan Ellis"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "jordan.ellis@example.com"
            ]
          },
          "mobile_number": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "4155550142"
            ]
          },
          "mobile_country_code": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "+1"
            ]
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_person_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "New"
            ]
          },
          "status_stage": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "open",
              "in_progress",
              "won",
              "lost",
              null
            ],
            "examples": [
              "open"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Website"
            ]
          },
          "assigned_to": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Alex Morgan"
            ]
          },
          "next_follow_up": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "Payment": {
        "type": "object",
        "title": "Payment",
        "description": "A payment as the Zapier \"New payment\" trigger sees it.",
        "required": [
          "id",
          "amount",
          "payment_type",
          "transaction_id",
          "cheque_number",
          "received_at",
          "paymentable_type",
          "paymentable_id",
          "customer_name",
          "customer_mobile",
          "comment",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "examples": [
              77310
            ]
          },
          "amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Amount received, in the workspace currency, as a plain number.",
            "examples": [
              149
            ]
          },
          "payment_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "The shop's label for the payment method.",
            "examples": [
              "Card"
            ]
          },
          "transaction_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reference from the payment provider or bank, as typed in by staff.",
            "examples": [
              "TXN-4187265091"
            ]
          },
          "cheque_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "received_at": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "paymentable_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "The kind of record this payment settles, for example `job` (a repair), `sale`, `amc_contract` or `rental_contract`.",
            "examples": [
              "job"
            ]
          },
          "paymentable_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Identifier of that record. For `job` it is the repair `id` returned by the repair trigger.",
            "examples": [
              48213
            ]
          },
          "customer_name": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Lucas Moreau"
            ]
          },
          "customer_mobile": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "170189900"
            ]
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "RepairStatusChange": {
        "type": "object",
        "title": "Repair status change",
        "description": "One entry in a repair's status history, as the Zapier \"Repair status changed\" trigger sees it.",
        "required": [
          "id",
          "job_id",
          "job_number",
          "status",
          "status_stage",
          "comment",
          "changed_by",
          "customer_name",
          "customer_mobile",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Identifier of the status change itself.",
            "examples": [
              195202
            ]
          },
          "job_id": {
            "type": "integer",
            "description": "The repair this change belongs to — the `id` from the repair trigger.",
            "examples": [
              48213
            ]
          },
          "job_number": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "JS-2026-0917"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "The status the repair moved **to**, in the shop's own label.",
            "examples": [
              "Ready for Delivery"
            ]
          },
          "status_stage": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "Open",
              "Closed",
              "Rejected",
              "Returned",
              null
            ],
            "examples": [
              "Open"
            ]
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "description": "Note entered with the change, if any.",
            "examples": [
              "Screen replaced and tested."
            ]
          },
          "changed_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display name of the staff member who made the change.",
            "examples": [
              "Sofia Rossi"
            ]
          },
          "customer_name": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Lucas Moreau"
            ]
          },
          "customer_mobile": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "170189900"
            ]
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "OutsourcedRepair": {
        "type": "object",
        "title": "Outsourced repair",
        "description": "A repair (or part of one) handed to an outside vendor, as the Zapier \"New outsourced repair\" trigger sees it.",
        "required": [
          "id",
          "job_id",
          "job_number",
          "vendor_name",
          "vendor_mobile",
          "vendor_email",
          "status",
          "price",
          "description",
          "device_type",
          "device_brand",
          "device_model",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "examples": [
              2044
            ]
          },
          "job_id": {
            "type": "integer",
            "description": "The repair this belongs to — the `id` from the repair trigger.",
            "examples": [
              48213
            ]
          },
          "job_number": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "JS-2026-0917"
            ]
          },
          "vendor_name": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Northside Micro Soldering"
            ]
          },
          "vendor_mobile": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2125550173"
            ]
          },
          "vendor_email": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "service@northsidemicro.example.com"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "Open",
              "Pending",
              "In Progress",
              "Completed",
              "Cancelled",
              null
            ],
            "examples": [
              "In Progress"
            ]
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "What the vendor charges, in the workspace currency.",
            "examples": [
              89.5
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Motherboard IC reballing"
            ]
          },
          "device_type": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Laptop"
            ]
          },
          "device_brand": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Apple"
            ]
          },
          "device_model": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "MacBook Air M2"
            ]
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "A unique key for this logical submission — a UUID is ideal. Reuse the same key when you retry after a\ntimeout or a network failure: the original outcome is returned with status `duplicate` and the original\n`request_id`, and no second record is created.\n\nKeys are scoped to your API key. A key that ended in a validation error is not remembered, so a corrected\nretry with the same key goes through. Currently, reusing a key with a **different** body also returns the\noriginal response rather than a `409`; send a fresh key for a genuinely new submission.\n",
        "schema": {
          "type": "string",
          "maxLength": 255,
          "examples": [
            "5f2c9c6e-1b1a-4b7e-9c2e-8e1d2f3a4b5c"
          ]
        }
      },
      "XBusiness": {
        "name": "X-Business",
        "in": "header",
        "required": false,
        "description": "The business (branch) to act on, as shown in the portal. Takes precedence over `business_id` in the body.\nOmit both to use the workspace's default business. An unknown value returns `404 business_not_found`; an\ninactive business returns `403 business_not_active`.\n",
        "schema": {
          "type": "integer",
          "examples": [
            1
          ]
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "How many of the most recent records to return. Newest first. There is no cursor or page.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 50
        }
      },
      "SearchTerm": {
        "name": "search_term",
        "in": "query",
        "required": true,
        "description": "Text to match anywhere in a customer's name, email or mobile number. Case-insensitive.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100,
          "examples": [
            "170189900"
          ]
        }
      },
      "AdapterKey": {
        "name": "adapter",
        "in": "path",
        "required": true,
        "description": "The provider whose payload shape is being posted. See the schema for what each adapter expects.",
        "schema": {
          "$ref": "#/components/schemas/AdapterKey"
        }
      },
      "JustdialAdapterKey": {
        "name": "adapter",
        "in": "path",
        "required": true,
        "description": "Must be `justdial`. This is the only adapter that accepts a `GET`, because Justdial delivers leads that way.",
        "schema": {
          "type": "string",
          "enum": [
            "justdial"
          ]
        }
      },
      "UrlToken": {
        "name": "token",
        "in": "path",
        "required": true,
        "description": "Your API key, carried in the URL. Only for providers whose panels cannot send a header — IndiaMART and\nJustdial. Treat the whole URL as a secret: anyone who has it can create leads in your workspace. Rotate the\nkey from the portal if the URL leaks. Every other caller should send the key in a header instead.\n",
        "schema": {
          "type": "string",
          "pattern": "^bp_[A-Za-z0-9]{40}$",
          "examples": [
            "bp_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t"
          ]
        }
      },
      "DestinationQuery": {
        "name": "destination",
        "in": "query",
        "required": false,
        "description": "Force the destination when the provider payload and the form configuration do not decide it.",
        "schema": {
          "$ref": "#/components/schemas/Destination"
        }
      },
      "FormIdQuery": {
        "name": "form_id",
        "in": "query",
        "required": false,
        "description": "Your identifier for the form, matching one configured for this key in the portal. Read for the `form` adapter when the body carries none.",
        "schema": {
          "type": "string",
          "maxLength": 191,
          "examples": [
            "website-contact"
          ]
        }
      },
      "FormTypeQuery": {
        "name": "form_type",
        "in": "query",
        "required": false,
        "description": "Which of the shop's forms to describe, exactly as returned in `form_types` by the same call without this\nparameter. Omit it to discover the available form types first.\n",
        "schema": {
          "type": "string",
          "examples": [
            "Lead"
          ]
        }
      },
      "TypeformSignature": {
        "name": "Typeform-Signature",
        "in": "header",
        "required": false,
        "description": "Sent by Typeform when a webhook secret is configured. Verified against the signing secret set for this key in\nthe portal; when no secret is set the header is ignored. A mismatch returns `401 invalid_signature`.\n",
        "schema": {
          "type": "string",
          "examples": [
            "sha256=Y2FsY3VsYXRlZC1obWFjLW9mLXRoZS1yYXctYm9keQ=="
          ]
        }
      },
      "TallySignature": {
        "name": "Tally-Signature",
        "in": "header",
        "required": false,
        "description": "Sent by Tally when a signing secret is configured. Verified against the signing secret set for this key in the\nportal; when no secret is set the header is ignored. A mismatch returns `401 invalid_signature`.\n",
        "schema": {
          "type": "string",
          "examples": [
            "sha256=Y2FsY3VsYXRlZC1obWFjLW9mLXRoZS1yYXctYm9keQ=="
          ]
        }
      }
    },
    "headers": {
      "XRequestId": {
        "description": "Identifier of this request. Quote it when contacting support. On intake routes it equals `request_id` in the body.",
        "schema": {
          "type": "string",
          "examples": [
            "01J9Z5X8K3Q4R7T2V9W1Y3A5B7"
          ]
        }
      },
      "XBusiness": {
        "description": "The business (branch) the request was resolved to.",
        "schema": {
          "type": "integer",
          "examples": [
            1
          ]
        }
      },
      "XRateLimitLimit": {
        "description": "Requests allowed per minute for this API key.",
        "schema": {
          "type": "integer",
          "examples": [
            60
          ]
        }
      },
      "XRateLimitRemaining": {
        "description": "Requests left in the current minute for this API key.",
        "schema": {
          "type": "integer",
          "examples": [
            57
          ]
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before trying again.",
        "schema": {
          "type": "integer",
          "examples": [
            23
          ]
        }
      }
    },
    "responses": {
      "SubmissionCreated": {
        "description": "Created. A new lead or self check-in exists; `data` is the record.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          },
          "X-Business": {
            "$ref": "#/components/headers/XBusiness"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/XRateLimitRemaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SubmissionResult"
            },
            "examples": {
              "Lead created": {
                "$ref": "#/components/examples/LeadCompleted"
              },
              "Self check-in created": {
                "$ref": "#/components/examples/SelfCheckInCompleted"
              }
            }
          }
        }
      },
      "SubmissionDuplicate": {
        "description": "Nothing new was created. Either this `Idempotency-Key` was already processed (the original outcome and\n`request_id` are returned), or the form's duplicate policy matched an existing record — for leads, one with the\nsame mobile number and country code or the same email; for self check-ins, an open one with the same mobile\nnumber. `data` is the existing record.\n",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          },
          "X-Business": {
            "$ref": "#/components/headers/XBusiness"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/XRateLimitRemaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SubmissionResult"
            },
            "examples": {
              "Existing lead matched": {
                "$ref": "#/components/examples/LeadDuplicate"
              },
              "Existing self check-in matched": {
                "$ref": "#/components/examples/SelfCheckInDuplicate"
              }
            }
          }
        }
      },
      "SubmissionReceived": {
        "description": "Accepted and parked. The form this came from is not configured yet in the portal (or its field mapping is\nstill a draft), so nothing was created. The raw submission is stored and listed in the portal's activity\nscreen, where it can be replayed once the form is set up. Do not retry.\n",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          },
          "X-Business": {
            "$ref": "#/components/headers/XBusiness"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/XRateLimitRemaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SubmissionResult"
            },
            "examples": {
              "Parked until the form is configured": {
                "$ref": "#/components/examples/SubmissionReceived"
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "No API key, or a key that is unknown, disabled or expired. Also returned when a provider signature does not\nverify.\n",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "No key sent": {
                "$ref": "#/components/examples/CredentialMissing"
              },
              "Unknown or expired key": {
                "$ref": "#/components/examples/CredentialInvalid"
              },
              "Provider signature mismatch": {
                "$ref": "#/components/examples/InvalidSignature"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key is valid but may not use this route — a non-Zapier key on a Zapier route, or a key created for one\nprovider posting through another provider's adapter. Also returned when the business named by `business_id`\nor `X-Business` is not active (`business_not_active`).\n",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "Key not permitted here": {
                "$ref": "#/components/examples/IntegrationNotPermitted"
              },
              "Business not active": {
                "$ref": "#/components/examples/BusinessNotActive"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The `{adapter}` is not a supported adapter (`unsupported_adapter`), the resource does not exist (`not_found`), or no business matches `business_id` / `X-Business` (`business_not_found`).",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "Unknown adapter": {
                "$ref": "#/components/examples/UnsupportedAdapter"
              },
              "Not found": {
                "$ref": "#/components/examples/NotFound"
              },
              "Unknown business": {
                "$ref": "#/components/examples/BusinessNotFound"
              }
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "Validation failed. `error.details` lists every failed rule, and nothing was created.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "examples": {
              "Missing contact details": {
                "$ref": "#/components/examples/ValidationFailed"
              },
              "Provider payload could not be read": {
                "$ref": "#/components/examples/UnreadableSubmission"
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "More than 60 requests in a minute for this API key. Wait `Retry-After` seconds, then back off exponentially.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/XRateLimitRemaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "Rate limit exceeded": {
                "$ref": "#/components/examples/RateLimitExceeded"
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Something failed on our side. Nothing about the cause is exposed; quote `request_id` to support.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "Internal error": {
                "$ref": "#/components/examples/InternalError"
              }
            }
          }
        }
      }
    },
    "examples": {
      "Ping": {
        "summary": "A WordPress key, connected",
        "value": {
          "status": "connected",
          "workspace": "Harbor Repair Co.",
          "provider": "WordPress"
        }
      },
      "ZapierPing": {
        "summary": "A Zapier key, connected",
        "value": {
          "status": "connected",
          "workspace": "Harbor Repair Co.",
          "provider": "Zapier",
          "service": "Zapier",
          "tenant": "1042"
        }
      },
      "LeadCreateRequest": {
        "summary": "Website enquiry with a device and an extra question",
        "value": {
          "form_id": "website-contact",
          "name": "Jordan Ellis",
          "email": "jordan.ellis@example.com",
          "mobile_number": "4155550142",
          "mobile_country_code": "+1",
          "source": "Website",
          "comment": "Screen cracked, touch still works. Need a quote for a replacement.",
          "device_type": "Mobile",
          "device_brand": "Apple",
          "device_model": "iPhone 15 Pro",
          "address": {
            "city": "San Francisco",
            "state": "CA",
            "zip_code": "94105"
          },
          "preferred_contact_time": "After 6 pm"
        }
      },
      "LeadCreateMinimalRequest": {
        "summary": "The minimum — a name and a mobile number",
        "value": {
          "name": "Noah Fischer",
          "mobile_number": "30901820",
          "mobile_country_code": "+49"
        }
      },
      "LeadCompleted": {
        "summary": "Lead created",
        "value": {
          "status": "completed",
          "request_id": "01J9Z5X8K3Q4R7T2V9W1Y3A5B7",
          "data": {
            "id": 10421,
            "object": "lead",
            "name": "Jordan Ellis",
            "email": "jordan.ellis@example.com",
            "mobile_number": "4155550142",
            "mobile_country_code": "+1",
            "phone_number": null,
            "contact_person_name": null,
            "status": "New",
            "status_stage": "open",
            "source": "Website",
            "assigned_to": "Alex Morgan",
            "next_follow_up": null,
            "comment": "Screen cracked, touch still works. Need a quote for a replacement.",
            "created_at": "2026-09-09T09:14:32.000000Z"
          }
        }
      },
      "LeadDuplicate": {
        "summary": "Existing lead returned",
        "value": {
          "status": "duplicate",
          "request_id": "01J9Z5X8K3Q4R7T2V9W1Y3A5B7",
          "data": {
            "id": 10421,
            "object": "lead",
            "name": "Jordan Ellis",
            "email": "jordan.ellis@example.com",
            "mobile_number": "4155550142",
            "mobile_country_code": "+1",
            "phone_number": null,
            "contact_person_name": null,
            "status": "Contacted",
            "status_stage": "in_progress",
            "source": "Website",
            "assigned_to": "Alex Morgan",
            "next_follow_up": "2026-09-12T04:30:00.000000Z",
            "comment": "Screen cracked, touch still works. Need a quote for a replacement.",
            "created_at": "2026-09-09T09:14:32.000000Z"
          }
        }
      },
      "SelfCheckInCreateRequest": {
        "summary": "A laptop booked in with a pickup",
        "value": {
          "form_id": "book-repair",
          "name": "Amelia Clarke",
          "email": "amelia.clarke@example.com",
          "mobile_number": "2079460958",
          "mobile_country_code": "+44",
          "device_type": "Laptop",
          "device_brand": "Apple",
          "device_model": "MacBook Air M2",
          "serial_number": "C02XK1ABJG5H",
          "accessories": [
            "Charger",
            "Laptop bag"
          ],
          "comment": "Does not power on after a liquid spill.",
          "is_recovery": true,
          "is_pickup_booked": true,
          "scheduled_on": "2026-09-10T05:00:00Z",
          "address": {
            "address_line": "48 Quayside Lane",
            "city": "London",
            "state": "Greater London",
            "zip_code": "SE1 2AA"
          }
        }
      },
      "SelfCheckInCompleted": {
        "summary": "Self check-in created",
        "value": {
          "status": "completed",
          "request_id": "01J9Z6A0M7N2P4Q6R8S0T2U4V6",
          "data": {
            "id": 3187,
            "object": "self_checkin",
            "name": "Amelia Clarke",
            "email": "amelia.clarke@example.com",
            "mobile_number": "2079460958",
            "mobile_country_code": "+44",
            "device_type": "Laptop",
            "device_brand": "Apple",
            "device_model": "MacBook Air M2",
            "serial_number": "C02XK1ABJG5H",
            "serial_number_2": null,
            "accessories": [
              "Charger",
              "Laptop bag"
            ],
            "comment": "Does not power on after a liquid spill.",
            "is_recovery": true,
            "is_pickup_booked": true,
            "scheduled_on": "2026-09-10T05:00:00.000000Z",
            "status": "Open",
            "custom_fields": null,
            "created_at": "2026-09-09T09:20:11.000000Z"
          }
        }
      },
      "SelfCheckInDuplicate": {
        "summary": "An open check-in for the same mobile number already exists",
        "value": {
          "status": "duplicate",
          "request_id": "01J9Z6A0M7N2P4Q6R8S0T2U4V6",
          "data": {
            "id": 3187,
            "object": "self_checkin",
            "name": "Amelia Clarke",
            "email": "amelia.clarke@example.com",
            "mobile_number": "2079460958",
            "mobile_country_code": "+44",
            "device_type": "Laptop",
            "device_brand": "Apple",
            "device_model": "MacBook Air M2",
            "serial_number": "C02XK1ABJG5H",
            "serial_number_2": null,
            "accessories": [
              "Charger",
              "Laptop bag"
            ],
            "comment": "Does not power on after a liquid spill.",
            "is_recovery": true,
            "is_pickup_booked": true,
            "scheduled_on": "2026-09-10T05:00:00.000000Z",
            "status": "Open",
            "custom_fields": null,
            "created_at": "2026-09-09T09:20:11.000000Z"
          }
        }
      },
      "SubmitLeadRequest": {
        "summary": "A contact form mapped to a lead in the portal",
        "description": "The form's own field names (`full_name`, `phone`, `message`) are translated by the mapping configured for `website-contact`.",
        "value": {
          "form_id": "website-contact",
          "data": {
            "full_name": "Jordan Ellis",
            "phone": "4155550142",
            "email": "jordan.ellis@example.com",
            "message": "Screen cracked, touch still works. Need a quote for a replacement.",
            "Preferred contact time": "After 6 pm"
          }
        }
      },
      "SubmitSelfCheckInRequest": {
        "summary": "Destination forced to a self check-in",
        "value": {
          "form_id": "book-repair",
          "destination": "self_checkin",
          "data": {
            "name": "Amelia Clarke",
            "mobile_number": "2079460958",
            "mobile_country_code": "+44",
            "device_type": "Laptop",
            "device_brand": "Apple",
            "device_model": "MacBook Air M2",
            "comment": "Does not power on after a liquid spill.",
            "is_pickup_booked": "yes"
          }
        }
      },
      "SubmissionReceived": {
        "summary": "Parked — form not configured yet",
        "value": {
          "status": "received",
          "request_id": "01J9Z6C4E1F3G5H7J9K1M3N5P7",
          "data": null
        }
      },
      "CustomFieldFormTypes": {
        "summary": "Forms that have custom fields",
        "value": {
          "form_types": [
            {
              "form_type": "Lead",
              "field_count": 2
            },
            {
              "form_type": "JobSheet",
              "field_count": 3
            }
          ]
        }
      },
      "CustomFieldDefinitions": {
        "summary": "The Lead form's custom fields",
        "value": {
          "form_type": "Lead",
          "fields": [
            {
              "field_name": "Preferred contact time",
              "field_type": "Dropdown",
              "placeholder": "When should we call?",
              "is_field_required": false,
              "select_box_items": [
                "Morning",
                "Afternoon",
                "After 6 pm"
              ]
            },
            {
              "field_name": "Warranty card number",
              "field_type": "Text",
              "placeholder": null,
              "is_field_required": true,
              "select_box_items": []
            }
          ]
        }
      },
      "IngestFormPayload": {
        "summary": "form — a plain HTML form post",
        "description": "Sent as `application/x-www-form-urlencoded`, `multipart/form-data` or JSON. Every field becomes a data field.",
        "value": {
          "form_id": "website-contact",
          "name": "Noah Fischer",
          "mobile_number": "30901820",
          "mobile_country_code": "+49",
          "message": "Battery drains in two hours. Is a replacement available for a Galaxy S24?"
        }
      },
      "IngestCanonicalPayload": {
        "summary": "canonical — the same envelope as /integrations/submit",
        "value": {
          "form_id": "website-contact",
          "destination": "lead",
          "data": {
            "name": "Noah Fischer",
            "mobile_number": "30901820",
            "mobile_country_code": "+49",
            "comment": "Battery drains in two hours. Is a replacement available for a Galaxy S24?"
          }
        }
      },
      "IngestTypeformPayload": {
        "summary": "typeform — Typeform's webhook payload",
        "description": "Answers are keyed by each question's `ref` (here `name`, `mobile`, `issue`) and mapped to lead fields in the portal.",
        "value": {
          "event_id": "01J9Z7ABCD",
          "event_type": "form_response",
          "form_response": {
            "form_id": "aB3dE9",
            "token": "8f1c6b2a9d3e",
            "submitted_at": "2026-09-09T09:30:12Z",
            "hidden": {
              "utm_source": "google"
            },
            "definition": {
              "id": "aB3dE9",
              "title": "Repair enquiry",
              "fields": [
                {
                  "id": "q1",
                  "title": "Your name",
                  "type": "short_text",
                  "ref": "name"
                },
                {
                  "id": "q2",
                  "title": "Mobile number",
                  "type": "phone_number",
                  "ref": "mobile"
                },
                {
                  "id": "q3",
                  "title": "What is wrong with the device?",
                  "type": "long_text",
                  "ref": "issue"
                }
              ]
            },
            "answers": [
              {
                "type": "text",
                "text": "Mia Nguyen",
                "field": {
                  "id": "q1",
                  "type": "short_text",
                  "ref": "name"
                }
              },
              {
                "type": "phone_number",
                "phone_number": "+61255501234",
                "field": {
                  "id": "q2",
                  "type": "phone_number",
                  "ref": "mobile"
                }
              },
              {
                "type": "text",
                "text": "Keyboard keys not responding on my MacBook Air M2.",
                "field": {
                  "id": "q3",
                  "type": "long_text",
                  "ref": "issue"
                }
              }
            ]
          }
        }
      },
      "IngestIndiaMartPayload": {
        "summary": "indiamart — IndiaMART Lead Manager push",
        "description": "Posted by IndiaMART to `/integrations/ingest/indiamart/{token}`. Always becomes a lead with source `IndiaMART`; the enquiry metadata is kept as custom fields.",
        "value": {
          "CODE": 200,
          "STATUS": "SUCCESS",
          "RESPONSE": {
            "UNIQUE_QUERY_ID": "2026090912345678",
            "QUERY_TYPE": "W",
            "QUERY_TIME": "2026-09-09 15:02:41",
            "SENDER_NAME": "Noah Fischer",
            "SENDER_MOBILE": "+49-30901820",
            "SENDER_EMAIL": "noah.fischer@example.com",
            "SENDER_COMPANY": "Fischer Elektronik GmbH",
            "SENDER_ADDRESS": "Mitte",
            "SENDER_CITY": "Berlin",
            "SENDER_STATE": "Berlin",
            "SENDER_PINCODE": "10115",
            "SENDER_COUNTRY_ISO": "DE",
            "SUBJECT": "Requirement for laptop motherboard repair",
            "QUERY_PRODUCT_NAME": "Laptop Repairing Service",
            "QUERY_MESSAGE": "Need repair for 12 XPS 13 laptops in our office.",
            "QUERY_MCAT_NAME": "Laptop Repair Services"
          }
        }
      },
      "RepairList": {
        "summary": "Two most recent repairs",
        "value": [
          {
            "id": 48213,
            "job_number": "JS-2026-0917",
            "status": "In Progress",
            "status_stage": "Open",
            "priority": "High",
            "customer_name": "Lucas Moreau",
            "customer_mobile": "170189900",
            "customer_email": "lucas.moreau@example.com",
            "device_type": "Mobile",
            "device_brand": "Samsung",
            "device_model": "Galaxy S24",
            "serial_number": "R5CX2K7L9MN",
            "total_amount": 320,
            "payment_status": "Partially Paid",
            "payment_received": 149,
            "assigned_to": "Sofia Rossi",
            "estimated_delivery": "2026-09-11T12:30:00.000000Z",
            "created_at": "2026-09-09T08:05:47.000000Z"
          },
          {
            "id": 48212,
            "job_number": "JS-2026-0916",
            "status": "Delivered",
            "status_stage": "Closed",
            "priority": "Normal",
            "customer_name": "Amelia Clarke",
            "customer_mobile": "2079460958",
            "customer_email": "amelia.clarke@example.com",
            "device_type": "Laptop",
            "device_brand": "Apple",
            "device_model": "MacBook Air M2",
            "serial_number": "C02XK1ABJG5H",
            "total_amount": 149,
            "payment_status": "Paid",
            "payment_received": 149,
            "assigned_to": "Daniel Kim",
            "estimated_delivery": null,
            "created_at": "2026-09-08T11:42:10.000000Z"
          }
        ]
      },
      "CustomerList": {
        "summary": "Two most recent customers",
        "value": [
          {
            "id": 882,
            "name": "Lucas Moreau",
            "email": "lucas.moreau@example.com",
            "mobile_number": "170189900",
            "mobile_country_code": "+33",
            "phone_number": null,
            "gst_number": null,
            "source": "Walk-in",
            "created_at": "2026-09-09T08:01:03.000000Z"
          },
          {
            "id": 881,
            "name": "Amelia Clarke",
            "email": "amelia.clarke@example.com",
            "mobile_number": "2079460958",
            "mobile_country_code": "+44",
            "phone_number": null,
            "gst_number": null,
            "source": "Website",
            "created_at": "2026-09-08T11:40:55.000000Z"
          }
        ]
      },
      "LeadList": {
        "summary": "Two most recent leads",
        "value": [
          {
            "id": 10421,
            "name": "Jordan Ellis",
            "email": "jordan.ellis@example.com",
            "mobile_number": "4155550142",
            "mobile_country_code": "+1",
            "phone_number": null,
            "contact_person_name": null,
            "status": "New",
            "status_stage": "open",
            "source": "Website",
            "assigned_to": "Alex Morgan",
            "next_follow_up": null,
            "comment": "Screen cracked, touch still works. Need a quote for a replacement.",
            "created_at": "2026-09-09T09:14:32.000000Z"
          },
          {
            "id": 10420,
            "name": "Moreau Design Studio",
            "email": "lucas.moreau@example.com",
            "mobile_number": "170189900",
            "mobile_country_code": "+33",
            "phone_number": null,
            "contact_person_name": "Lucas Moreau",
            "status": "Quoted",
            "status_stage": "in_progress",
            "source": "Referral",
            "assigned_to": "Alex Morgan",
            "next_follow_up": "2026-09-11T04:30:00.000000Z",
            "comment": "Need repair for 12 XPS 13 laptops in our office.",
            "created_at": "2026-09-09T09:32:41.000000Z"
          }
        ]
      },
      "PaymentList": {
        "summary": "Two most recent payments",
        "value": [
          {
            "id": 77310,
            "amount": 149,
            "payment_type": "Card",
            "transaction_id": "TXN-4187265091",
            "cheque_number": null,
            "received_at": "2026-09-09T08:20:00.000000Z",
            "paymentable_type": "job",
            "paymentable_id": 48213,
            "customer_name": "Lucas Moreau",
            "customer_mobile": "170189900",
            "comment": "Advance",
            "created_at": "2026-09-09T08:20:14.000000Z"
          },
          {
            "id": 77309,
            "amount": 149,
            "payment_type": "Cash",
            "transaction_id": null,
            "cheque_number": null,
            "received_at": "2026-09-08T13:05:00.000000Z",
            "paymentable_type": "job",
            "paymentable_id": 48212,
            "customer_name": "Amelia Clarke",
            "customer_mobile": "2079460958",
            "comment": null,
            "created_at": "2026-09-08T13:05:22.000000Z"
          }
        ]
      },
      "RepairStatusChangeList": {
        "summary": "Two most recent status changes",
        "value": [
          {
            "id": 195202,
            "job_id": 48213,
            "job_number": "JS-2026-0917",
            "status": "Ready for Delivery",
            "status_stage": "Open",
            "comment": "Screen replaced and tested.",
            "changed_by": "Sofia Rossi",
            "customer_name": "Lucas Moreau",
            "customer_mobile": "170189900",
            "created_at": "2026-09-09T10:48:09.000000Z"
          },
          {
            "id": 195201,
            "job_id": 48213,
            "job_number": "JS-2026-0917",
            "status": "In Progress",
            "status_stage": "Open",
            "comment": null,
            "changed_by": "Sofia Rossi",
            "customer_name": "Lucas Moreau",
            "customer_mobile": "170189900",
            "created_at": "2026-09-09T08:10:30.000000Z"
          }
        ]
      },
      "OutsourcedRepairList": {
        "summary": "Most recent outsourced repair",
        "value": [
          {
            "id": 2044,
            "job_id": 48212,
            "job_number": "JS-2026-0916",
            "vendor_name": "Northside Micro Soldering",
            "vendor_mobile": "2125550173",
            "vendor_email": "service@northsidemicro.example.com",
            "status": "In Progress",
            "price": 89.5,
            "description": "Motherboard IC reballing",
            "device_type": "Laptop",
            "device_brand": "Apple",
            "device_model": "MacBook Air M2",
            "created_at": "2026-09-08T12:15:00.000000Z"
          }
        ]
      },
      "CustomerSearchResult": {
        "summary": "Customers matching \"0189\"",
        "value": [
          {
            "id": 882,
            "name": "Lucas Moreau",
            "email": "lucas.moreau@example.com",
            "mobile_number": "170189900",
            "mobile_country_code": "+33",
            "phone_number": null,
            "gst_number": null,
            "source": "Walk-in",
            "created_at": "2026-09-09T08:01:03.000000Z"
          }
        ]
      },
      "CredentialMissing": {
        "summary": "No key sent",
        "value": {
          "error": {
            "code": "credential_missing",
            "message": "An API key is required.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9K0",
            "documentation_url": "https://developers.bytephase.com/errors#credential_missing"
          },
          "message": "An API key is required."
        }
      },
      "CredentialInvalid": {
        "summary": "Unknown, disabled or expired key",
        "value": {
          "error": {
            "code": "credential_invalid",
            "message": "The API key is invalid, inactive or expired.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9K1",
            "documentation_url": "https://developers.bytephase.com/errors#credential_invalid"
          },
          "message": "The API key is invalid, inactive or expired."
        }
      },
      "InvalidSignature": {
        "summary": "Provider signature did not verify",
        "value": {
          "error": {
            "code": "invalid_signature",
            "message": "Invalid signature.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9K2",
            "documentation_url": "https://developers.bytephase.com/errors#invalid_signature"
          },
          "message": "Invalid signature."
        }
      },
      "IntegrationNotPermitted": {
        "summary": "Key may not use this route",
        "value": {
          "error": {
            "code": "integration_not_permitted",
            "message": "This API key is not allowed to access this endpoint.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9K3",
            "documentation_url": "https://developers.bytephase.com/errors#integration_not_permitted"
          },
          "message": "This API key is not allowed to access this endpoint."
        }
      },
      "UnsupportedAdapter": {
        "summary": "Unknown adapter in the URL",
        "value": {
          "error": {
            "code": "unsupported_adapter",
            "message": "Unsupported adapter.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9K4",
            "documentation_url": "https://developers.bytephase.com/errors#unsupported_adapter"
          },
          "message": "Unsupported adapter."
        }
      },
      "NotFound": {
        "summary": "Not found",
        "value": {
          "error": {
            "code": "not_found",
            "message": "The requested resource was not found.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9K5",
            "documentation_url": "https://developers.bytephase.com/errors#not_found"
          },
          "message": "The requested resource was not found."
        }
      },
      "ValidationFailed": {
        "summary": "Neither email nor mobile number was sent",
        "value": {
          "error": {
            "code": "validation_failed",
            "message": "The request contains invalid data.",
            "details": [
              {
                "field": "email",
                "message": "The email field is required when mobile number is not present."
              },
              {
                "field": "mobile_number",
                "message": "The mobile number field is required when email is not present."
              }
            ],
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9K6",
            "documentation_url": "https://developers.bytephase.com/errors#validation_failed"
          },
          "message": "The request contains invalid data."
        }
      },
      "UnreadableSubmission": {
        "summary": "The adapter could not read the provider payload",
        "value": {
          "error": {
            "code": "unreadable_submission",
            "message": "Unable to read this submission.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9K7",
            "documentation_url": "https://developers.bytephase.com/errors#unreadable_submission"
          },
          "message": "Unable to read this submission."
        }
      },
      "RateLimitExceeded": {
        "summary": "Over 60 requests this minute",
        "value": {
          "error": {
            "code": "rate_limit_exceeded",
            "message": "Too many requests.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9K8",
            "documentation_url": "https://developers.bytephase.com/errors#rate_limit_exceeded"
          },
          "message": "Too many requests."
        }
      },
      "InternalError": {
        "summary": "Our fault",
        "value": {
          "error": {
            "code": "internal_error",
            "message": "Something went wrong on our side. Quote the request id when contacting support.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9K9",
            "documentation_url": "https://developers.bytephase.com/errors#internal_error"
          },
          "message": "Something went wrong on our side. Quote the request id when contacting support."
        }
      },
      "BusinessNotFound": {
        "summary": "Unknown business named in business_id or X-Business",
        "value": {
          "error": {
            "code": "business_not_found",
            "message": "Business not found.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9KA",
            "documentation_url": "https://developers.bytephase.com/errors#business_not_found"
          },
          "message": "Business not found."
        }
      },
      "BusinessNotActive": {
        "summary": "The business named is not active",
        "value": {
          "error": {
            "code": "business_not_active",
            "message": "This business is not active.",
            "request_id": "01J9Z8A1B2C3D4E5F6G7H8J9KB",
            "documentation_url": "https://developers.bytephase.com/errors#business_not_active"
          },
          "message": "This business is not active."
        }
      }
    }
  },
  "x-tagGroups": [
    {
      "name": "Connection",
      "tags": [
        "Connection"
      ]
    },
    {
      "name": "Intake",
      "tags": [
        "Intake"
      ]
    },
    {
      "name": "Zapier",
      "tags": [
        "Zapier"
      ]
    }
  ]
}