Edynamics API
Developer Portal →

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

ScopeGrants
read:clients / write:clientsRead or modify the clients table
read:leads / write:leadsRead or create leads
read:invoices / write:invoicesRead or create invoices
read:content / write:contentRead or schedule content posts
read:workspaces / write:workspacesRead or provision workspaces
read:analyticsPlatform + per-client KPI snapshots
webhooks:manageList, 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": "..." } }

StatusCodeMeaning
400bad_requestMissing/invalid params
401unauthorizedMissing/invalid key
403forbiddenKey lacks required scope
404not_foundResource missing or org-scoped away
429rate_limitedPer-key request cap hit
500internal_errorUnhandled — 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.provisioned

Payload

{
  "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.