{"openapi":"3.1.0","info":{"title":"Fernway API","version":"1.0.0","description":"Send leads into GoHighLevel and track each one like a shipment. Every lead gets a waybill number and a scan for every step."},"servers":[{"url":"https://fernway.jeromebadour.com"}],"security":[{"key":[]}],"components":{"securitySchemes":{"key":{"type":"http","scheme":"bearer","description":"An API key, \"fw_\" then two parts. Shown once when it is made."}}},"paths":{"/api/v1/leads":{"post":{"operationId":"sendLead","summary":"Send a lead","description":"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.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"idempotencyKey":{"type":"string","minLength":8,"maxLength":200,"description":"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."},"route":{"type":"string","pattern":"^[a-z0-9-]{1,40}$","description":"Which route the lead takes: the pipeline, stage and tags it lands in. Set up per account."},"slot":{"description":"Booking routes only: the meeting time, ISO 8601 with its offset, e.g. 2026-11-12T14:30:00+03:00.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"contact":{"type":"object","properties":{"firstName":{"description":"Up to 80 characters.","type":"string","minLength":1,"maxLength":80},"lastName":{"description":"Up to 80 characters.","type":"string","minLength":1,"maxLength":80},"email":{"description":"Email or phone is required. GHL matches the contact on it, so one person is one contact.","type":"string"},"phone":{"description":"With its country code, e.g. +1 503 555 0142. Spaces, dots and brackets are fine.","type":"string"},"companyName":{"description":"Up to 120 characters.","type":"string","minLength":1,"maxLength":120},"website":{"description":"Up to 200 characters.","type":"string","minLength":1,"maxLength":200},"message":{"description":"Up to 2,000 characters. Added to the contact in GHL as a note.","type":"string","minLength":1,"maxLength":2000}},"additionalProperties":false,"description":"The person. Only these fields; anything else is refused."}},"required":["idempotencyKey","route","contact"],"additionalProperties":false},"example":{"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."}}}}},"responses":{"200":{"description":"Sent before with this idempotencyKey: the same waybill.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"duplicate":{"type":"boolean","description":"True when this idempotencyKey was sent before: the same waybill, nothing sent twice."},"number":{"type":"string","pattern":"^FW \\d{2} \\d{5}$","description":"The waybill number, e.g. \"FW 26 00817\"."},"state":{"type":"string","enum":["picked_up","in_transit","waiting","signed","held","returned"],"description":"picked_up, in_transit, waiting (a retry is scheduled), signed (GHL has it), held (needs you), returned."},"links":{"type":"object","properties":{"self":{"type":"string","description":"This lead in the API: GET it for every scan."},"receipt":{"type":"string","format":"uri","description":"A public receipt page for this lead, valid 30 days, safe to hand to the person who sent it."}},"required":["self","receipt"],"additionalProperties":false}},"required":["ok","duplicate","number","state","links"],"additionalProperties":false}}}},"201":{"description":"Picked up. Delivery to GHL happens next, usually within seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"duplicate":{"type":"boolean","description":"True when this idempotencyKey was sent before: the same waybill, nothing sent twice."},"number":{"type":"string","pattern":"^FW \\d{2} \\d{5}$","description":"The waybill number, e.g. \"FW 26 00817\"."},"state":{"type":"string","enum":["picked_up","in_transit","waiting","signed","held","returned"],"description":"picked_up, in_transit, waiting (a retry is scheduled), signed (GHL has it), held (needs you), returned."},"links":{"type":"object","properties":{"self":{"type":"string","description":"This lead in the API: GET it for every scan."},"receipt":{"type":"string","format":"uri","description":"A public receipt page for this lead, valid 30 days, safe to hand to the person who sent it."}},"required":["self","receipt"],"additionalProperties":false}},"required":["ok","duplicate","number","state","links"],"additionalProperties":false},"example":{"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..."}}}}},"400":{"description":"The body isn't JSON.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":false},"problems":{"type":"array","items":{"type":"string"},"description":"What to change, one line each, the field first: \"contact.email: a real email address\"."}},"required":["ok","problems"],"additionalProperties":false}}}},"401":{"description":"No API key, or not a live one.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":false},"problems":{"type":"array","items":{"type":"string"},"description":"What to change, one line each, the field first: \"contact.email: a real email address\"."}},"required":["ok","problems"],"additionalProperties":false}}}},"422":{"description":"The lead was refused at the door; nothing was made. Each problem names its field and what the field needs.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":false},"problems":{"type":"array","items":{"type":"string"},"description":"What to change, one line each, the field first: \"contact.email: a real email address\"."}},"required":["ok","problems"],"additionalProperties":false},"example":{"ok":false,"problems":["contact.phone: a phone number with country code, like +1 503 555 0142"]}}}},"429":{"description":"Over the limit: 60 requests a minute per key. Retry-After gives the wait in seconds; the same idempotencyKey sends nothing twice.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":false},"problems":{"type":"array","items":{"type":"string"},"description":"What to change, one line each, the field first: \"contact.email: a real email address\"."}},"required":["ok","problems"],"additionalProperties":false}}}}}}},"/api/v1/leads/{number}":{"get":{"operationId":"getLead","summary":"Track a lead","description":"Where a lead is, and every scan so far.","parameters":[{"name":"number","in":"path","required":true,"description":"The waybill number, dashed: \"FW-26-00817\".","schema":{"type":"string","pattern":"^FW-\\d{2}-\\d{5}$"}}],"responses":{"200":{"description":"The lead and its scans.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"lead":{"type":"object","properties":{"number":{"type":"string","pattern":"^FW \\d{2} \\d{5}$","description":"The waybill number, e.g. \"FW 26 00817\"."},"state":{"type":"string","enum":["picked_up","in_transit","waiting","signed","held","returned"],"description":"picked_up, in_transit, waiting (a retry is scheduled), signed (GHL has it), held (needs you), returned."},"pickedUpAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"route":{"type":"string"},"holdReason":{"type":["string","null"]},"meeting":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}]},"scans":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["picked_up","checked","handed","signed","retry_scheduled","held","returned","resent"]},"step":{"description":"contact, note, appointment or opportunity, when the scan is about one GHL step.","type":["string","null"]},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"try":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"status":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"GHL's HTTP status for this try, when there was one."},"note":{"description":"Why it waits or was held, in GHL's own words when GHL gave a reason.","type":["string","null"]}},"required":["kind","step","at","try","status","note"],"additionalProperties":false}}},"required":["number","state","pickedUpAt","route","holdReason","meeting","scans"],"additionalProperties":false}},"required":["ok","lead"],"additionalProperties":false},"example":{"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}]}}}}},"401":{"description":"No API key, or not a live one.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":false},"problems":{"type":"array","items":{"type":"string"},"description":"What to change, one line each, the field first: \"contact.email: a real email address\"."}},"required":["ok","problems"],"additionalProperties":false}}}},"404":{"description":"No lead with that number on this key's account.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":false},"problems":{"type":"array","items":{"type":"string"},"description":"What to change, one line each, the field first: \"contact.email: a real email address\"."}},"required":["ok","problems"],"additionalProperties":false}}}},"429":{"description":"Over the limit: 60 requests a minute per key. Retry-After gives the wait in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":false},"problems":{"type":"array","items":{"type":"string"},"description":"What to change, one line each, the field first: \"contact.email: a real email address\"."}},"required":["ok","problems"],"additionalProperties":false}}}}}}}}}