Developers
REST API and webhooks
Authentication
Create a key in Settings → API & webhooks. Keys start with grw_ and are shown once, so store yours somewhere safe. A key acts as the user who created it, with that user's role, in that workspace. Revoke it from the same page.
Send the key as a bearer token. The base URL is your own Growlyne address:
curl https://crm.yourcompany.com/api/v1/me \
-H "Authorization: Bearer grw_your_key"Read-only users
403.Endpoints
:object is an object key: contacts, companies, deals, tickets, or the key of a custom object. GET /api/v1/objects lists them all with their property keys.
| Method | Path | Does |
|---|---|---|
| GET | /api/v1/me | The key's user, role and workspace |
| GET | /api/v1/objects | Objects, their properties and pipelines, and the workspace users |
| GET | /api/v1/records/:object | List records. Query: limit, offset, search, filters (JSON), sort=prop:asc|desc, listId |
| GET | /api/v1/records/:object/:id | One record with its associations, tasks and emails |
| POST | /api/v1/records/:object | Create a record: { values, associate? } |
| PATCH | /api/v1/records/:object/:id | Update a record: { values } |
| DELETE | /api/v1/records/:object/:id | Archive a record |
| POST | /api/v1/records/:object/:id/notes | Add a note to the timeline: { body, subject? } |
| GET | /api/v1/tasks | Your open tasks |
| POST | /api/v1/ops | Run any operation the app uses: { op, args } |
Examples
Create a contact
curl -X POST https://crm.yourcompany.com/api/v1/records/contacts \
-H "Authorization: Bearer grw_your_key" \
-H "content-type: application/json" \
-d '{ "values": { "email": "sam@example.com", "firstname": "Sam" } }'Search deals, newest first
curl "https://crm.yourcompany.com/api/v1/records/deals?search=renewal&sort=createdAt:desc&limit=50" \
-H "Authorization: Bearer grw_your_key"Add a note to a record
curl -X POST https://crm.yourcompany.com/api/v1/records/contacts/REC_ID/notes \
-H "Authorization: Bearer grw_your_key" \
-H "content-type: application/json" \
-d '{ "subject": "Call", "body": "Discussed the Q1 rollout." }'Run an operation
POST /api/v1/ops exposes the operations the app itself uses, such as record.merge, record.associate, record.bulkUpdate or task.upsert. An unknown name returns 400 with the list of available operations.
curl -X POST https://crm.yourcompany.com/api/v1/ops \
-H "Authorization: Bearer grw_your_key" \
-H "content-type: application/json" \
-d '{ "op": "record.merge", "args": { … } }'Property keys depend on your workspace: read them from GET /api/v1/objects before writing.
Responses, errors and limits
Successful responses are JSON in the shape { "data": … }. Errors are { "error": "message" } with a matching status:
| Status | Meaning |
|---|---|
400 | Invalid input, or an unknown operation |
401 | Missing or invalid key |
402 | The workspace plan does not include the API |
403 | Your role does not allow this |
404 | No such route or record |
429 | Rate limit reached |
The rate limit is 600 requests per minute per user.
Webhooks
Workspace admins add webhooks in Settings → API & webhooks: an HTTPS URL and the events to receive. Workflows can also call a webhook as one of their actions.
Events :
| Event | When |
|---|---|
record.created | A contact, company, deal, ticket or custom record is created (app, API, form, import, inbox) |
record.updated | A property changes. The body has the record and the list of changes |
record.deleted | A record is deleted for good |
deal.stage_changed | A deal moves to another stage, with the previous and new stage ids |
form.submitted | Someone submits one of your forms |
Each delivery is a POST with a JSON body { "event", "data", "sentAt" } and these headers:
| Header | Value |
|---|---|
growlyne-event | The event name |
growlyne-delivery | A unique id for this delivery, handy to ignore repeats |
growlyne-signature | Hex HMAC-SHA256 of the raw body, keyed with the webhook's secret |
Verify the signature before trusting a payload:
import { createHmac, timingSafeEqual } from "node:crypto";
const expected = createHmac("sha256", process.env.GROWLYNE_WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["growlyne-signature"]));Answer with any 2xx within 10 seconds. A failed delivery (network error, timeout or another status) is retried up to 5 times with growing delays, and recent deliveries are listed in settings with their status.