Connek Public API

v1 · HMAC-signed
operational

HMAC-authenticated API for partner integrations.

Server-to-server only. Use this surface to manage Connek resources (clients, leads, services, bookings, quotes, invoices) on behalf of a business you have a partner key for. Browser-side calls are never supported — the secret must stay on your backend.

1 · Authentication

Every request must carry three headers. The signature proves you hold the secret without putting the secret on the wire.

X-Connek-Key-Idyour public key id (ck_live_…)
X-Connek-Timestampunix seconds; ±5 min anti-replay window
X-Connek-Signaturehex(hmac_sha256(secret, canonical))

Canonical string (newline-joined):

{METHOD}
{PATH}
{UNIX_TIMESTAMP}
{HEX(SHA256(BODY))}

2 · Quick start

METHOD=GET
PATH=/v1/services
TS=$(date +%s)
BODY=""
BODY_SHA=$(printf "%s" "$BODY" | openssl dgst -sha256 -hex | awk '{print $2}')
CANONICAL=$(printf "%s\n%s\n%s\n%s" "$METHOD" "$PATH" "$TS" "$BODY_SHA")
SIG=$(printf "%s" "$CANONICAL" | openssl dgst -sha256 -hmac "$CONNEK_SECRET" -hex | awk '{print $2}')

curl -X "$METHOD" "https://api.dev.connek.ca$PATH" \
  -H "X-Connek-Key-Id: $CONNEK_KEY_ID" \
  -H "X-Connek-Timestamp: $TS" \
  -H "X-Connek-Signature: $SIG"
import hashlib, hmac, json, os, time, requests

KEY_ID = os.environ["CONNEK_KEY_ID"]
SECRET = os.environ["CONNEK_SECRET"]
BASE   = "https://api.dev.connek.ca"

def sign(method: str, path: str, body: bytes = b"") -> dict[str, str]:
    ts = str(int(time.time()))
    body_sha = hashlib.sha256(body).hexdigest()
    canonical = f"{method}\n{path}\n{ts}\n{body_sha}".encode()
    sig = hmac.new(SECRET.encode(), canonical, hashlib.sha256).hexdigest()
    return {
        "X-Connek-Key-Id":   KEY_ID,
        "X-Connek-Timestamp": ts,
        "X-Connek-Signature": sig,
    }

r = requests.get(BASE + "/v1/services", headers=sign("GET", "/v1/services"))
print(r.status_code, r.json())
import { createHash, createHmac } from "node:crypto";

const KEY_ID = process.env.CONNEK_KEY_ID;
const SECRET = process.env.CONNEK_SECRET;
const BASE   = "https://api.dev.connek.ca";

function sign(method, path, body = "") {
  const ts = String(Math.floor(Date.now() / 1000));
  const bodySha = createHash("sha256").update(body, "utf8").digest("hex");
  const canonical = `${method}\n${path}\n${ts}\n${bodySha}`;
  const sig = createHmac("sha256", SECRET).update(canonical, "utf8").digest("hex");
  return {
    "X-Connek-Key-Id":   KEY_ID,
    "X-Connek-Timestamp": ts,
    "X-Connek-Signature": sig,
  };
}

const r = await fetch(`${BASE}/v1/services`, { headers: sign("GET", "/v1/services") });
console.log(r.status, await r.json());

3 · Endpoints

All paths are under /v1. Full schema in /docs / openapi.json.

MethodPathScope
GET/v1/servicesservices:read
POST/v1/servicesservices:write
GET/v1/clientsclients:read
POST/v1/clientsclients:write
GET/v1/leadsleads:read
POST/v1/leadsleads:write
GET/v1/bookingsbookings:read
POST/v1/bookingsbookings:write
GET/v1/quotesquotes:read
POST/v1/quotesquotes:write
GET/v1/invoicesinvoices:read
POST/v1/invoicesinvoices:write
GET/v1/availabilityavailability:read

4 · Error envelope

All non-2xx responses follow the same shape:

{
  "detail": {
    "error": {
      "code": "missing_field",
      "message": "client_email is required"
    }
  }
}

Status codes you should handle: 401 (bad/expired signature), 403 (key missing scope), 404 (no resource), 409 (state conflict), 422 (validation), 429 (rate limited — 100 req/min/key), 5xx (back off and retry).

5 · Documentation

Reference completa por tema en el repo: