Skip to main content

Neon CRM Connector (Technical Reference)

Written by Philippe Trussart

Overview

The Neon CRM connector extracts objects from the Neon CRM v2 REST API and lands them in your data lake. It discovers the set of streams and their schemas from an internal mapping and then, for each stream, lists all record IDs via the collection endpoint and fetches each record by ID. The run model is full-replace per stream: every sync re-reads all IDs for the selected streams and rewrites their rows. There is no incremental windowing or delete propagation in this connector.


Authentication & Setup

Provide:

  • Organization ID (org_id)

  • API Key (api_key)

Requests use HTTP Basic authentication with org_id as the username and api_key as the password against the base URL https://api.neoncrm.com/v2. The built-in Test action validates credentials by calling /accounts with a 1-row page.


Sync Strategy

Discovery

  • The connector loads a table map from an internal schema provider and exposes those streams for discovery and sync.

  • For each selected stream, the connector emits the schema (field names and types) and unique keys derived from the stream’s indexes.

Full-replace extraction

For each stream in the active catalog:

  1. Emit schema & keys (from the table map).

  2. List all record IDs for the stream using either:

    • GET /{table} with pagination, or

    • GET /accounts/{accountId}/{table} for tables that require an account context (see “Account-scoped streams” below).

  3. Fetch each record by ID via GET /{table}/{id} (or the account-scoped path) and write it.

  4. Emit a record_count metric for the stream.

This results in a complete refresh of the stream’s rows each run. There are no incremental bookmarks and the connector does not emit deletes.

Account-scoped streams

Some listing endpoints require an Account ID (e.g., patterns like /accounts/{accountId}/memberships). For those streams, the connector:

  • First fetches all account IDs, combining individual and company accounts from /accounts?userType=INDIVIDUAL and /accounts?userType=COMPANY.

  • Then enumerates the child records per account via /accounts/{accountId}/{table} and aggregates all child IDs before fetching details.

  • When fetching a single account record by ID, the response shape is normalized from either individualAccount or companyAccount.

Special handling: Custom fields

For the customFields stream, the listing API requires a category query param. The connector loops over the categories:

Account, Donation, Event, Attendee, Individual, Company, Product, Prospect, Grant

…collects IDs per category, de-duplicates them, then fetches each by ID.

Pagination

  • Collection endpoints use page parameters currentPage (0-based) and pageSize (default 200).

  • The connector increments pages until currentPage reaches pagination.totalPages.

Retries & rate limits

  • 429 Too Many Requests → the connector logs the condition, sleeps for RETRY_DELAY (default 30s), and retries automatically.

  • All requests have retry with backoff, up to RETRY_LIMIT attempts (default 5).

  • 4xx/5xx responses produce descriptive errors (401/403/404 have custom messages; other statuses raise a generic error including the URI and params).

State & deletes

  • State/bookmarks: none — each run fully replaces rows per stream.

  • Deletes: none are emitted; downstream tables are refreshed by overwrite/merge.


Primary Keys & Uniqueness

  • Each stream declares one or more unique keys via its indexes entry in the stream’s table map.

  • The default record ID field is id (or accountId for accounts); some streams override this with a different key when listing IDs.

  • Key properties are emitted along with the schema for merge/upsert consistency.


Error Handling & Record Shaping

  • Before writing, responses are pruned to the declared columns; any unmapped fields are dropped, and missing mapped fields are set to null.

  • The connector normalizes account detail responses, selecting either individualAccount or companyAccount payloads.


Streams

  • accounts

    • Listing: GET /accounts (paged; run twice with userType=INDIVIDUAL and userType=COMPANY when collecting IDs for child streams).

    • Detail: GET /accounts/{accountId}; payload normalized from individualAccount/companyAccount.

    • Keys: from the table map indexes (typically accountId).

  • customFields

    • Listing: GET /customFields?category=<…> for each of: Account, Donation, Event, Attendee, Individual, Company, Product, Prospect, Grant.

    • Detail: GET /customFields/{id}.

    • Keys: from the table map indexes.

  • Account-scoped child streams (example pattern: memberships, but the exact set is defined in the table map)

    • Listing: GET /accounts/{accountId}/{table} for each account.

    • Detail: GET /{table}/{id}.

    • Keys: from the table map indexes.

For every stream above, the connector emits the schema and unique keys from the table map and writes all columns exactly as defined there.


Metrics

After each stream completes, the connector emits:

  • record_count (counter): total rows written for that stream.


Known Limitations & Notes

  • No incremental sync: runs are full-replace per stream; plan scheduling accordingly for large accounts.

  • Account-scoped streams may multiply API volume (per-account iteration).

  • Custom fields require category-based listing; if a category is restricted, that subset may be missing.

  • HTTP errors propagate with descriptive messages; 429s are retried with delay.

  • Columns & types are constrained to the table map; unexpected fields in responses are dropped.

Did this answer your question?