Corteksa

Best Practices

CRM — Best Practices

The recommended way to build fast, correct CRM integrations.

✓ Key record bodies by field slug

The data object is keyed by each field's slug (name-8fk2), never the display name. Fetch slugs once from GET /object/:objectSlug/fields and cache them. Using the label throws 400 "Field 'X' is required".

✓ Use slugs everywhere, never numeric ids

Objects, records, fields, and relations are all addressed by slug. Slugs are stable across dev/staging/prod and safe to log. Never hard-code or expose an internal id.

✓ Read field metadata to build inputs

List/detail responses include a fields[] block (types + options + virtual relation fields). Render your form from it instead of hard-coding field names — that way a new custom field appears automatically.

✓ Send only changed fields on update

PUT accepts a partial data. Send just the fields that changed; the mutation service emits oldData/newData with only the diff, which keeps audit logs, workflows, and webhooks precise.

✓ Validate select values against the field's options

select / multi_select / status / priority / tag values must match an allowed option. Fetch them from the field-options endpoint rather than guessing.

✓ Paginate and filter server-side

GET /object/data/:objectSlug takes page / limit (default 25, capped) plus a filters array. Push filters and sorting to the server; never fetch a whole object and filter in the client. Cap filter conditions (max 50 per request).

✓ Treat import/export as async jobs

Large exports/imports return a jobSlug. Poll the status endpoint and show progress; don't block the UI on the initial request.

✓ Scope API keys to the minimum

Grant a key only the objects and verbs it needs (read.contacts, create.deals), not a broad *. The key acts as its owner and is still filtered by that owner's A/G/M/D level per record — but a tight scope is defense in depth.

✓ Prefer webhooks/realtime over polling

Subscribe to record.* webhooks (server → your app) or the DataEventsGateway (server → UI) for changes. Make webhook handlers idempotent (dedupe on X-Corteksa-Delivery) and verify the signature.

✗ Don't reach past the pinned connection (backend)

On the shared hyper-tenant DB, all record access must go through the pinned repoProvider; Bull processors use @TenantScoped(). A raw DataSource query bypasses RLS and returns zero rows or fails the insert. See Backend Integration.

✗ Don't re-implement validation or access filtering

Field validation belongs in the FieldTypeRegistry handlers; row-level scope belongs in access-filter.builder.ts. Adding a new field type means adding a handler, not a special case in a service.

On this page