FERNWAY

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.

Header
Authorization: Bearer fw_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Limits

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

#FieldTypeAbout
1idempotencyKeyrequiredstringYour own id for this lead, 8 to 200 characters, like an order or form-entry id. Sent again, it gives the same waybill back.
2routerequiredstringWhich route the lead takes: the pipeline, stage and tags it lands in. Set up per account.
3slotstring (date-time)Booking routes only: the meeting time, ISO 8601 with its offset, e.g. 2026-11-12T14:30:00+03:00.
4contactrequiredobjectThe person. Only these fields; anything else is refused.
5contact.firstNamestringUp to 80 characters.
6contact.lastNamestringUp to 80 characters.
7contact.emailstringEmail or phone is required. GHL matches the contact on it, so one person is one contact.
8contact.phonestringWith its country code, e.g. +1 503 555 0142. Spaces, dots and brackets are fine.
9contact.companyNamestringUp to 120 characters.
10contact.websitestringUp to 200 characters.
11contact.messagestringUp 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.

Request body
{
  "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."
  }
}
201 answer
{
  "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

#StatusMeans
1201Picked up. Delivery to GHL happens next, usually within seconds.
2200Sent before with this idempotencyKey: the same waybill.
3400The body isn't JSON.
4401No API key, or not a live one.
5422The lead was refused at the door; nothing was made. Each problem names its field and what the field needs.
6429Over the limit: 60 requests a minute per key. Retry-After gives the wait in seconds; the same idempotencyKey sends nothing twice.
422 answer
{
  "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.

200 answer
{
  "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

#StatusMeans
1200The lead and its scans.
2401No API key, or not a live one.
3404No lead with that number on this key's account.
4429Over 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.