Developer reference
Binnacle AI API
Read your fleet, credentials, voyages, and compliance data over a versioned REST API. Push events into Slack, Zapier, HubSpot, or your own systems with HMAC-signed webhooks. No SDK install required — every endpoint is plain HTTPS + JSON.
Authentication
Every request to /api/v1/* must include an Authorization header. Tokens look like bnk_live_ followed by 32 characters and are issued from Admin → API keys.
curl https://binnacleai.com/api/v1/vessels \ -H "Authorization: Bearer bnk_live_yourtokengoeshere"
Tokens are bcrypt-hashed at rest. The cleartext is shown once at creation and is not recoverable — store it in a secrets manager.
Pagination + envelope
List endpoints accept limit (default 50, max 200) and offset (default 0). Responses use a consistent envelope:
{
"data": [...],
"pagination": { "limit": 50, "offset": 0, "total": 245 },
"links": {
"self": "https://binnacleai.com/api/v1/vessels?limit=50&offset=0",
"next": "https://binnacleai.com/api/v1/vessels?limit=50&offset=50",
"prev": null
}
}Rate limits
100 requests per minute per key, with a short burst allowance up to 200 req/min. Every successful response carries:
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 73 X-RateLimit-Reset: 1714780320
When exceeded, the API returns 429 with Retry-After in seconds.
Errors
{ "error": "InvalidApiKey", "message": "Provide Authorization: Bearer ..." }401 InvalidApiKey— missing/malformed Authorization header401 RevokedApiKey/ExpiredApiKey404 NotFound— out-of-org or missing record429 RateLimitExceeded503 InternalError— transient
Endpoints
Sample: GET /api/v1/vessels
curl https://binnacleai.com/api/v1/vessels?limit=2 \
-H "Authorization: Bearer bnk_live_..."
{
"data": [
{
"id": "ckxv...",
"name": "Pacific Explorer",
"official_number": "1234567",
"subchapter": "T",
"home_port": "Honolulu, HI",
"coi_expiration": "2026-08-15T00:00:00.000Z"
}
],
"pagination": { "limit": 2, "offset": 0, "total": 14 },
"links": { "self": "...", "next": "...", "prev": null }
}Webhooks
Subscribe to compliance events from Admin → Webhooks. Pick events, point at your endpoint, and we'll POST a signed JSON payload every time the event fires. We retry failed deliveries at 1m, 5m, 30m, and 2h — after 5 consecutive failures the subscription is auto-disabled.
Available events
CREDENTIAL_EXPIRING— 30/7/0 days before a credential expires.CREDENTIAL_EXPIRED— Credential past expiration.DRILL_OVERDUE— Required drill not logged within window.DRILL_LOGGED— New drill record posted.INCIDENT_CREATED— Any incident created.INCIDENT_CLOSED— Incident moved to closed.COI_EXPIRING— Vessel CoI within 90 days of expiry.CYBER_INCIDENT_REPORTED— 46 CFR 101.620 cyber incident reported.WORK_REST_VIOLATION— Sub M / STCW work-rest threshold exceeded.OFAC_MATCH_FLAGGED— OFAC SDN screening returned a positive match.INSURANCE_RENEWAL_DUE— Policy entering renewal notice window.SURVEY_OVERDUE— Class survey past due date.PORT_CALL_PLANNED— New port call scheduled.CREW_ASSIGNED— Crew member assigned to a vessel.VOYAGE_DEPARTED— Voyage transitioned to UNDERWAY.VOYAGE_ARRIVED— Voyage transitioned to COMPLETED.ALL— Wildcard — receive every event.
Sample payload
POST /your/endpoint
Content-Type: application/json
User-Agent: Binnacle-Webhook/1.0
X-Binnacle-Event: CREDENTIAL_EXPIRING
X-Binnacle-Delivery-Id: 4f3c2b...
X-Binnacle-Signature: sha256=8a7c4f...
X-Binnacle-Timestamp: 2026-04-22T18:00:00.000Z
{
"event": "CREDENTIAL_EXPIRING",
"orgId": "org_abc",
"timestamp": "2026-04-22T18:00:00.000Z",
"data": {
"credential": {
"id": "cred_123",
"type": "STCW Basic Training",
"expiration_date": "2026-05-22T00:00:00.000Z",
"days_left": 30
},
"crew_member_id": "crew_456"
}
}Verify the signature (Node.js)
import crypto from "node:crypto";
function verify(secret, rawBody, headerSig) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
// Constant-time compare to defeat timing attacks.
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expected}`),
Buffer.from(headerSig)
);
}
// Express:
app.post("/binnacle", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(process.env.BINNACLE_SECRET, req.body, req.header("X-Binnacle-Signature"))) {
return res.status(401).send("bad signature");
}
const event = JSON.parse(req.body.toString());
// ... handle event
res.status(200).send("ok");
});Verify the signature (Python)
import hmac, hashlib
def verify(secret: str, raw_body: bytes, header_sig: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header_sig)
# Flask:
@app.post("/binnacle")
def binnacle():
sig = request.headers.get("X-Binnacle-Signature", "")
if not verify(os.environ["BINNACLE_SECRET"], request.get_data(), sig):
abort(401)
event = request.get_json()
# ... handle event
return "ok", 200Sandbox + support
Want a sandbox key with seed data so you can wire a Zap or test your endpoint without touching production?
Request a sandbox key