Custom Integrations
Any language that can make an HTTPS request can create leads and self check-ins. Create an integration with the provider type Website or REST API (both are intake-only), copy the key, and pick one of the two request styles below.
Two request styles
Section titled “Two request styles”Flat: /leads and /self-checkin
Section titled “Flat: /leads and /self-checkin”Send BytePhase’s field names directly at the top level of the body. Simplest for your own code.
POST /api/integrations/leadsPOST /api/integrations/self-checkin{ "name": "Jordan Ellis", "mobile_number": "4155550142", "mobile_country_code": "+1", "email": "jordan.ellis@example.com", "comment": "Screen flickers after boot"}Optional on both: business_id (integer; defaults to the workspace’s default business) and form_id (to apply a form’s field map and duplicate rule).
Envelope: /submit
Section titled “Envelope: /submit”Wrap the fields in data and say which form and destination they belong to. This is what the WordPress plugin sends, and the right choice when the field names come from a form you do not control and are mapped in BytePhase.
POST /api/integrations/submit{ "form_id": "contact-us", "destination": "lead", "data": { "Your Name": "Jordan Ellis", "Phone": "4155550142", "Message": "Screen flickers after boot" }}| Field | Required | Meaning |
|---|---|---|
data |
Yes | The submitted fields — BytePhase’s names, or your own names if a field map exists for the form |
form_id |
No | Which of the integration’s forms this is; picks the field map and destination. Up to 191 characters. |
destination |
No | lead or self_checkin. Overrides the form’s destination. May also be given as ?destination= in the URL. |
business_id |
No | Integer; defaults to the workspace’s default business |
If neither destination nor a matching form can determine where the submission should go, it is parked as 202 received until the form is set up in BytePhase — never rejected. See Submissions & Troubleshooting.
The same envelope is also accepted at POST /api/integrations/ingest/canonical, and a plain form-encoded or multipart POST (an HTML <form> posting straight to BytePhase) is accepted at POST /api/integrations/ingest/form, where every field except form_id and destination is treated as data.
Response
Section titled “Response”All three endpoints return the same shape:
| HTTP | status |
data |
|---|---|---|
201 |
completed |
The created lead or self check-in |
200 |
duplicate |
The existing record (idempotent replay, or the duplicate rule matched) |
202 |
received |
null — parked until the form is mapped |
{ "status": "completed", "request_id": "01K4R4Z2N7Q9X0F3M6C8V1B5D2", "data": { "id": 10421, "object": "lead", "…": "…" }}Always send an Idempotency-Key header (a UUID per submission) so a retry is safe. See Idempotency.
Lead fields
Section titled “Lead fields”| Field | Rules |
|---|---|
name |
Required. Up to 60 characters |
mobile_number |
Required unless email is sent. Up to 20 characters, digits as entered |
email |
Required unless mobile_number is sent. Valid email, up to 255 |
mobile_country_code |
Dialling code, e.g. "+1" or "+44". Up to 10 |
phone_number |
Landline or alternate number. Up to 20 |
contact_person_name |
For business customers. Up to 60 |
source |
Where the lead came from, e.g. "Website". Up to 100. Matched to the workspace’s lead sources by name |
next_follow_up |
Date or date-time |
comment |
Free text |
device_type, device_brand, device_model |
Names, up to 100 each. Matched to the workspace’s catalogue by name; a brand or model that is not in the catalogue is kept as typed |
address |
Object: address_line (200), city (100), state (100), zip_code (20) |
custom_fields |
Object of "label": "value" pairs, values up to 1000 characters |
Self check-in fields
Section titled “Self check-in fields”| Field | Rules |
|---|---|
name |
Required. Up to 255 |
mobile_number |
Required unless email is sent. Up to 20 |
email |
Required unless mobile_number is sent. Up to 255 |
mobile_country_code |
Up to 10 |
device_type, device_brand, device_model |
Names, up to 255 each; unknown brands/models kept as typed |
serial_number, serial_number_2 |
Up to 255 / 191 |
device_password |
Up to 191. Stored for the technician; never returned by the API |
accessories |
Array of strings, or a comma-separated string |
comment |
Free text |
is_recovery |
Boolean — data recovery job |
is_pickup_booked |
Boolean — customer wants pickup |
scheduled_on |
Date or date-time the customer plans to come in |
address |
Object: address_line, city, state (255 each), zip_code (20) |
custom_fields |
Object of "label": "value" pairs |
Exact response field lists for both records are in the reference.
Unrecognised fields become custom fields
Section titled “Unrecognised fields become custom fields”Any top-level field that is not in the tables above is not rejected. Scalar values are kept on the record as custom fields (up to 20 of them, each trimmed to 1000 characters), so a “How did you hear about us?” or “Preferred time” question your form asks is preserved without any configuration. Send a field that is the wrong type or too long and you get 422 validation_failed naming it.
If the workspace has defined its own custom fields for leads or self check-ins, you can fetch their definitions to render them on your form: GET /api/integrations/custom-fields lists the form types that have fields, and GET /api/integrations/custom-fields?form_type=… returns that type’s field names, types, placeholders and options. See the reference for the response.
Samples
Section titled “Samples”Replace harbor-repair and the key. Every sample sends an Idempotency-Key and treats completed, duplicate and received as success.
curl -X POST "https://harbor-repair.api.bytephase.com/api/integrations/leads" \ -H "Authorization: Bearer bp_your_api_key" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Jordan Ellis", "mobile_number": "4155550142", "mobile_country_code": "+1", "email": "jordan.ellis@example.com", "device_type": "Laptop", "device_brand": "Dell", "comment": "Screen flickers after boot", "source": "Website" }'Node.js
Section titled “Node.js”import { randomUUID } from 'node:crypto';
const BASE_URL = 'https://harbor-repair.api.bytephase.com/api';const API_KEY = process.env.BYTEPHASE_API_KEY;
export async function createLead(lead) { const response = await fetch(`${BASE_URL}/integrations/leads`, { method: 'POST', headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json', Accept: 'application/json', 'Idempotency-Key': randomUUID(), }, body: JSON.stringify(lead), });
const body = await response.json();
if (!response.ok) { // body.error.code is stable; body.error.request_id is what support needs. throw new Error(`${body.error.code} (${body.error.request_id}): ${body.error.message}`); }
return body; // { status: 'completed' | 'duplicate' | 'received', request_id, data }}
await createLead({ name: 'Jordan Ellis', mobile_number: '4155550142', mobile_country_code: '+1', email: 'jordan.ellis@example.com', comment: 'Screen flickers after boot', source: 'Website',});<?php
$baseUrl = 'https://harbor-repair.api.bytephase.com/api';$apiKey = getenv('BYTEPHASE_API_KEY');
$lead = [ 'name' => 'Jordan Ellis', 'mobile_number' => '4155550142', 'mobile_country_code' => '+1', 'email' => 'jordan.ellis@example.com', 'comment' => 'Screen flickers after boot', 'source' => 'Website',];
$idempotencyKey = bin2hex(random_bytes(16));
$ch = curl_init("{$baseUrl}/integrations/leads");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Content-Type: application/json', 'Accept: application/json', "Idempotency-Key: {$idempotencyKey}", ], CURLOPT_POSTFIELDS => json_encode($lead),]);
$raw = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);curl_close($ch);
$body = json_decode($raw, true);
if ($status >= 400) { throw new RuntimeException(sprintf( '%s (%s): %s', $body['error']['code'], $body['error']['request_id'], $body['error']['message'], ));}
// $body['status'] is completed, duplicate or received; $body['data'] is the lead (or null when received).Python
Section titled “Python”import osimport uuid
import requests
BASE_URL = "https://harbor-repair.api.bytephase.com/api"API_KEY = os.environ["BYTEPHASE_API_KEY"]
def create_lead(lead: dict) -> dict: response = requests.post( f"{BASE_URL}/integrations/leads", json=lead, headers={ "Authorization": f"Bearer {API_KEY}", "Accept": "application/json", "Idempotency-Key": str(uuid.uuid4()), }, timeout=15, ) body = response.json()
if not response.ok: error = body["error"] raise RuntimeError(f"{error['code']} ({error['request_id']}): {error['message']}")
return body # {"status": "completed" | "duplicate" | "received", "request_id": ..., "data": ...}
create_lead( { "name": "Jordan Ellis", "mobile_number": "4155550142", "mobile_country_code": "+1", "email": "jordan.ellis@example.com", "comment": "Screen flickers after boot", "source": "Website", })A self check-in
Section titled “A self check-in”curl -X POST "https://harbor-repair.api.bytephase.com/api/integrations/self-checkin" \ -H "Authorization: Bearer bp_your_api_key" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Noah Fischer", "mobile_number": "30901820", "mobile_country_code": "+49", "device_type": "Mobile", "device_brand": "Samsung", "device_model": "Galaxy S24", "serial_number": "R5CW1234567", "accessories": ["Charger", "Case"], "comment": "Cracked screen, touch still works", "is_pickup_booked": true, "scheduled_on": "2026-09-12" }'Checklist before going live
Section titled “Checklist before going live”- Key created with the Website or REST API type — never Zapier. Why
- Key stored server-side only, never in browser JavaScript or a mobile app binary
-
Idempotency-Keyon every write, reused on retry - Retry only
429,423,5xxand network errors, with backoff. Rate Limits -
completed,duplicateandreceivedall treated as success for the visitor -
request_idlogged on your side for every request - Tested against a trial workspace first. Environments

