API Reference
Programmatic access to Edynamics. All endpoints are JSON over HTTPS, scoped per organization, and authenticated with a bearer token.
Base URL: https://edynamicsgrowth.com/api/v1
Authentication
Clients generate keys for their own workspace under your portal → Developers; org-wide admin keys are managed at /admin/api → API Keys. The key is shown once on creation; revoke and rotate if you lose it. Pass it as a Bearer header on every request. A workspace key is confined to its own leads, invoices, content and analytics.
curl https://edynamicsgrowth.com/api/v1/clients \
-H "Authorization: Bearer edyn_sk_..."Scopes
| Scope | Grants |
|---|---|
read:clients / write:clients | Read or modify the clients table |
read:leads / write:leads | Read or create leads |
read:invoices / write:invoices | Read or create invoices |
read:content / write:content | Read or schedule content posts |
read:workspaces / write:workspaces | Read or provision workspaces |
read:analytics | Platform + per-client KPI snapshots |
webhooks:manage | List, create, delete webhooks |
Rate limits
100 requests per minute per key. Burst tolerated; sustained over-limit returns 429 Too Many Requests.
Error codes
Errors return JSON: { "error": { "code": "...", "message": "..." } }
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | Missing/invalid params |
| 401 | unauthorized | Missing/invalid key |
| 403 | forbidden | Key lacks required scope |
| 404 | not_found | Resource missing or org-scoped away |
| 429 | rate_limited | Per-key request cap hit |
| 500 | internal_error | Unhandled — please report |
Clients
GET/v1/clients · list (read:clients) — supports ?limit= and ?offset=.
GET/v1/clients/{id} · fetch one (read:clients).
POST/v1/clients · create (write:clients). Fires client.created.
PATCH/v1/clients/{id} · update (write:clients). Fires client.updated.
# Create
curl -X POST https://edynamicsgrowth.com/api/v1/clients \
-H "Authorization: Bearer edyn_sk_..." \
-H "Content-Type: application/json" \
-d '{"business_name":"Acme Dental","owner_email":"jess@acme.dental"}'Leads
GET/v1/leads · list (read:leads). Filters: client_id, status.
GET/v1/leads/{id} · fetch one (read:leads).
POST/v1/leads · create (write:leads). Fires lead.captured.
curl -X POST https://edynamicsgrowth.com/api/v1/leads \
-H "Authorization: Bearer edyn_sk_..." \
-H "Content-Type: application/json" \
-d '{"client_id":"<uuid>","name":"John Doe","email":"john@example.com"}'Workspaces
GET/v1/workspaces · list (read:workspaces).
GET/v1/workspaces/{id} · fetch one (read:workspaces).
POST/v1/workspaces/provision · provision a workspace for an existing client (write:workspaces). Fires workspace.provisioned.
Invoices
GET/v1/invoices · list (read:invoices).
GET/v1/invoices/{id} · fetch one (read:invoices).
POST/v1/invoices · create draft (write:invoices). Fires invoice.created.
Analytics
GET/v1/analytics/overview · platform-wide 30-day KPIs (read:analytics).
GET/v1/analytics/clients/{id} · per-client 30-day KPIs (read:analytics).
Webhooks (CRUD)
GET/v1/webhooks · list (webhooks:manage).
POST/v1/webhooks · create. The signing secret is in the response body — store it; subsequent fetches don't return it.
DELETE/v1/webhooks/{id} · soft-delete.
Webhook events
POST to your registered URL whenever any of these fire:
client.created client.updated
lead.captured lead.converted
booking.created booking.confirmed booking.completed
invoice.created invoice.paid
quote.approved
content.published
nps.responded
churn.risk_detected
workspace.provisionedPayload
{
"event": "lead.captured",
"timestamp": "2026-01-01T00:00:00Z",
"data": {
"id": "uuid",
"client_id": "uuid",
"name": "John Doe",
"email": "john@example.com",
...
}
}Verifying signatures
Every webhook carries X-Edynamics-Signature: sha256={hmac}, the HMAC-SHA256 of the raw request body with your webhook secret. Verify with constant-time comparison.
import crypto from 'node:crypto'
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody).digest('hex')
const got = req.headers['x-edynamics-signature'].replace(/^sha256=/, '')
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got))) {
res.status(400).end()
}Retries
Any non-2xx response (or network error) schedules a retry: 5m, then 30m, then 2h. After the third failure on the same payload the webhook is paused and the admin gets a notification — re-enable it from /admin/api → Webhooks.
SDKs
Official SDKs are tracked at github.com/edynamics-founder.