Docs · API reference
Fernway API v1
Two calls: send a lead, and track it. JSON in and out, at https://fernway.jeromebadour.com. The same reference as a file: OpenAPI 3.1.
Authentication
Send your key as a bearer token on every request. A key belongs to one account and reaches only that account’s leads. It is shown once when it is made; Fernway keeps only its hash, so a lost key is replaced, not recovered. A revoked key stops at once.
Authorization: Bearer fw_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxLimits
60 requests a minute per key. Over it, the answer is 429 with a Retry-After header in seconds. The public demo key is shared, so it is limited per visitor instead: 20 leads an hour.
POST/api/v1/leads
Send a lead. Picks the lead up, checks it and queues its delivery into GHL. The same idempotencyKey sent again gives the same waybill back, with nothing sent twice.
Body
| # | Field | Type | About |
|---|---|---|---|
| 1 | idempotencyKeyrequired | string | Your own id for this lead, 8 to 200 characters, like an order or form-entry id. Sent again, it gives the same waybill back. |
| 2 | routerequired | string | Which route the lead takes: the pipeline, stage and tags it lands in. Set up per account. |
| 3 | slot | string (date-time) | Booking routes only: the meeting time, ISO 8601 with its offset, e.g. 2026-11-12T14:30:00+03:00. |
| 4 | contactrequired | object | The person. Only these fields; anything else is refused. |
| 5 | contact.firstName | string | Up to 80 characters. |
| 6 | contact.lastName | string | Up to 80 characters. |
| 7 | contact.email | string | Email or phone is required. GHL matches the contact on it, so one person is one contact. |
| 8 | contact.phone | string | With its country code, e.g. +1 503 555 0142. Spaces, dots and brackets are fine. |
| 9 | contact.companyName | string | Up to 120 characters. |
| 10 | contact.website | string | Up to 200 characters. |
| 11 | contact.message | string | Up to 2,000 characters. Added to the contact in GHL as a note. |
Send only these fields. An unknown field is refused, so a typo can’t drop data without you knowing.
{
"idempotencyKey": "order-1001",
"route": "trial",
"contact": {
"firstName": "Ada",
"lastName": "Byron",
"email": "ada@example.com",
"phone": "+1 503 555 0142",
"companyName": "Analytical Co",
"message": "Looking for a 30-day trial."
}
}{
"ok": true,
"duplicate": false,
"number": "FW 26 00817",
"state": "picked_up",
"links": {
"self": "/api/v1/leads/FW-26-00817",
"receipt": "https://fernway.jeromebadour.com/track/eyJ..."
}
}Answers
| # | Status | Means |
|---|---|---|
| 1 | 201 | Picked up. Delivery to GHL happens next, usually within seconds. |
| 2 | 200 | Sent before with this idempotencyKey: the same waybill. |
| 3 | 400 | The body isn't JSON. |
| 4 | 401 | No API key, or not a live one. |
| 5 | 422 | The lead was refused at the door; nothing was made. Each problem names its field and what the field needs. |
| 6 | 429 | Over the limit: 60 requests a minute per key. Retry-After gives the wait in seconds; the same idempotencyKey sends nothing twice. |
{
"ok": false,
"problems": [
"contact.phone: a phone number with country code, like +1 503 555 0142"
]
}GET/api/v1/leads/{number}
Track a lead. Where a lead is, and every scan so far. The number goes in the path with dashes: FW-26-00817.
{
"ok": true,
"lead": {
"number": "FW 26 00817",
"state": "signed",
"pickedUpAt": "2026-11-10T12:04:11.000Z",
"route": "Trial requests",
"holdReason": null,
"meeting": null,
"scans": [
{
"kind": "picked_up",
"step": null,
"at": "2026-11-10T12:04:11.000Z",
"try": null,
"status": null,
"note": null
},
{
"kind": "checked",
"step": null,
"at": "2026-11-10T12:04:11.000Z",
"try": null,
"status": null,
"note": null
},
{
"kind": "retry_scheduled",
"step": "contact",
"at": "2026-11-10T12:04:12.000Z",
"try": 1,
"status": 429,
"note": "Too many requests"
},
{
"kind": "handed",
"step": "contact",
"at": "2026-11-10T12:04:42.000Z",
"try": 2,
"status": 201,
"note": null
},
{
"kind": "signed",
"step": null,
"at": "2026-11-10T12:04:43.000Z",
"try": 2,
"status": null,
"note": null
}
]
}
}Answers
| # | Status | Means |
|---|---|---|
| 1 | 200 | The lead and its scans. |
| 2 | 401 | No API key, or not a live one. |
| 3 | 404 | No lead with that number on this key's account. |
| 4 | 429 | Over the limit: 60 requests a minute per key. Retry-After gives the wait in seconds. |
Scans
A lead’s history, oldest first. Each scan says what happened, to which GHL step, on which try, with GHL’s status.
picked_up- Fernway took the lead in.
checked- The lead passed its checks.
handed- A step went to GHL and GHL took it.
signed- Every step is in GHL. The lead is delivered.
retry_scheduled- GHL was busy, down or silent; the next try is booked.
held- GHL refused it for a reason a retry can't fix.
returned- Out of tries after 24 hours.
resent- Someone sent a held lead again after fixing it.