Skip to main content

Vibe code an app integration between SigParser and your CRM

Example prompt for quickly setting up an integration between SigParser and whatever CRM or app you rhave.

# Generate an integration with AI

SigParser's API gives you clean contact and company records enriched with relationship intelligence — who on your team knows a person, how strongly, how often they talk, when they last met. Getting that into your CRM usually means waiting for a native connector.

You don't have to wait. An AI coding agent (**Claude Code** or **OpenAI Codex**) can read SigParser's OpenAPI spec, read your CRM's OpenAPI spec, interview you about what you want, and write a complete, working, schedulable sync app — typically in under an hour.

This article gives you the prompt to do it.

---

## What you'll end up with

A small TypeScript (or Python, if you prefer) application that:

- Pulls new and changed contacts and companies from SigParser on a schedule
- Filters out the records you don't want cluttering your CRM
- Maps SigParser fields to your CRM's fields through a config file you can edit without touching code
- Creates or updates records in your CRM — **never** creating duplicates
- Remembers where it left off, so nightly runs only process what actually changed
- Logs every decision so you can explain why any given record is or isn't in your CRM

This is the same architecture as the SigParser → Airtable connector our team built. The prompt below encodes the lessons from that build.

---

## Before you start

You'll need four things:

**1. An AI coding agent**

- [Claude Code](https://claude.com/claude-code) — install with `npm install -g @anthropic-ai/claude-code`, then run `claude` in an empty folder
- [OpenAI Codex](https://openai.com/codex) — either CLI works; the prompt is agent-agnostic

**2. Your SigParser API key**

In SigParser, go to **Settings → API Keys** and create one. The prompt tells the agent to verify it with `GET /api/v2/user/me` before doing anything else.

**3. Your CRM's API credentials and its OpenAPI spec**

Almost every modern CRM publishes one. A few common ones:

| CRM | OpenAPI / API reference |
|---|---|
| HubSpot | `https://api.hubspot.com/api-catalog-public/v1/apis` |
| Pipedrive | `https://developers.pipedrive.com/docs/api/v1/openapi.yaml` |
| Salesforce | Object metadata via `/services/data/vXX.X/sobjects/` (no single spec file) |
| Zoho CRM | Published per-module on their developer portal |
| Attio | `https://api.attio.com/openapi/api` |
| Close | `https://api.close.com/api/openapi.json` |
| Copper, Insightly, Freshsales, Monday, Notion, Airtable… | See each vendor's developer docs |

If your CRM has no machine-readable spec, a link to its REST documentation works — the agent will read the docs pages instead. Tell it so in the prompt.

**4. A sandbox / test environment**

Every major CRM offers a free developer or sandbox instance. **Build against that first.** Do not point a freshly generated integration at your production CRM until you've watched it do a dry run and written a handful of test records.

---

## The prompt

Create an empty folder, start your agent in it, and paste this in.

````text
You are going to build a production-quality data integration that syncs contacts
and companies from SigParser into my CRM. Work carefully and ask me questions
before you write code.

## Step 1 — Read the SigParser API spec

Fetch and read: https://ipaas.sigparser.com/swagger/v2/swagger.json

Key facts about the SigParser API you must respect:

- Base URL: https://ipaas.sigparser.com
- Auth header: `Authorization: Bearer {SIGPARSER_API_KEY}`
- List endpoints paginate with an envelope:
`{ "has_more": false, "next_url": "https://...", "data": [ {...} ] }`
While `has_more` is true, follow `next_url`. When it goes false, SAVE the
final `next_url` — that is the cursor. On the next run, start from that saved
URL and you get only what changed since. This is how incremental sync works;
do not invent your own "updated since" filtering.
- `GET /api/v2/contacts/delta/all?take=500&start_now=false` — contacts changed
since the cursor. `start_now=false` on the very first call means "from the
beginning of time", i.e. a full backfill.
- `GET /api/v2/companies/delta/all?take=500&start_now=false` — same for companies.
- `GET /api/v2/companies?domain=acme.com` — fetch one company by domain. Use
this when a contact needs to be linked to a company that isn't in the CRM yet.
- `GET /api/v2/contacts/fields` and `GET /api/v2/companies/fields` — the full
field catalogue, including any custom fields defined on my SigParser tenant.
Expose this as a CLI command so I can discover field names later.
- `GET /api/v2/user/me` — call this at startup to verify the API key works and
fail fast with a clear message if it doesn't.
- Gotcha: contact relationship fields are named `relationships_*` (PLURAL),
company relationship fields are `relationship_*` (SINGULAR). Getting this
backwards silently produces empty columns. Verify each field name against
/fields output rather than guessing.

The SigParser fields that matter most — the reason people build this
integration — are the relationship and interaction fields:
relationships_strongest_name / relationships_strongest (who on my team has
the strongest relationship with this person, and their email)
relationships_most_active_name, relationships_first_name,
relationships_latest_name, relationships_coworkers_names,
relationships_coworkers
interactions_total, interactions_emails_to, interactions_emails_from,
interactions_meetings_completed, interactions_total_first,
interactions_total_latest, interactions_meetings_upcoming_next
Plus the firmographic and contact detail fields: name_*, job_title, job_level,
job_function, phone_*, profile_linkedin_url, location_*, company_name,
company_website, email_address_domain, industry, company_employees_range.

## Step 2 — Ask me for my CRM's API spec

Stop and ask me:

1. Which CRM am I syncing into, and what is the URL of its OpenAPI/Swagger spec
(or its REST API documentation, if it has no machine-readable spec)?
2. How do I authenticate to it — API key, personal access token, OAuth2
client credentials? What header or flow does it use?
3. What are the instance/account/base identifiers it needs in the URL, if any?

Then FETCH AND READ that spec before writing any code. Specifically, extract and
tell me what you found for:

- The object/record types that correspond to contacts (people) and companies
(accounts/organisations), and their exact API paths.
- Create, update, and search/query endpoints for each.
- Whether it has a native upsert (create-or-update on an external id). If it
does, use it — it's the safest way to avoid duplicates. If it doesn't, you
will need a search-then-create-or-update path.
- Whether it supports a custom "external id" field, and how to create one.
- Batch write limits (how many records per call).
- Rate limits, and what the response looks like when you hit one (status code,
Retry-After header, lockout duration).
- How records are linked to each other (contact → company association).
- Its field/schema discovery endpoint, so the app can validate my field mapping
against the live CRM instead of failing mid-run.

If anything is ambiguous in the spec, ask me rather than guessing.

## Step 3 — Interview me about what I want

Ask me these, in one batch, with your recommended default for each so I can just
say "defaults are fine":

**Scope**
- Sync contacts, companies, or both?
- Which SigParser fields do I want in the CRM? (Offer the relationship +
interaction set above as the recommended starting point.)
- Should the sync create new records in my CRM, or only enrich records that
already exist there? (Enrich-only is a very common requirement — many teams
do not want their CRM filling up with every person who ever emailed them.)

**Filtering — which records are worth syncing**
- Only `email_address_type == "Person"`? (Recommended yes — this excludes
distribution lists and machine addresses.)
- Exclude `record_status` of "Ignore" and "Coworker"? (Recommended yes.)
- Exclude role addresses like info@, support@, noreply@? (Recommended yes.)
- Exclude addresses SigParser has seen bounce? (Recommended yes.)
- Minimum interaction count before a contact is worth a CRM record? (Default 1.)
- Exclude public email domains (gmail.com, yahoo.com) from becoming COMPANY
records? (Recommended yes — nobody wants a "gmail.com" account record.)
- Restrict to specific domains, or to contacts owned by specific team members?

**Write behaviour**
- Which field is the merge key? (Recommended: store the SigParser `record_id` in
a dedicated custom field on the CRM side and merge on that. Secondary match on
email for contacts / domain for companies, so records that already existed
before the sync was installed get ADOPTED rather than duplicated.)
- For each field: always overwrite, or write only when creating the record?
(Some fields should be seeded from SigParser then owned by a human.)
- Should an empty SigParser value be allowed to blank out a CRM field?
(Recommended NO, by default. Humans type in the CRM too.)
- Should contacts be linked/associated to their company record? Should a missing
company be created automatically?

**Operations**
- How often will this run — nightly, hourly?
- Where will it run — my laptop via Task Scheduler/cron, a container, a VM?
- TypeScript on Node 20+, or Python 3.11+? (Default TypeScript.)

## Step 4 — Build it

Non-negotiable design rules. These come from a production SigParser connector
and exist because each one was learned the hard way:

1. **Field mapping is DATA, not code.** All of it lives in a `mapping.json`
file. If adding a new synced column requires me to edit a `.ts`/`.py` file,
you have designed it wrong. Each entry looks like:
```json
{
"source": "relationships_strongest_name",
"target": "Strongest Relationship",
"type": "text",
"transform": "coalesce",
"separator": ", ",
"writeOn": "always",
"skipIfEmpty": true
}
```
`source` accepts an array of SigParser field names — first non-empty wins.
Support these `type` coercions: text, longText, number, integer, boolean,
date, dateTime, email, url, phone, singleSelect, multipleSelects, json —
coercing to whatever the CRM's own type system requires.
Support these `transform` values: coalesce (default), join, first, count,
lowercase, uppercase, domain (reduce a URL or email to a bare host),
dateOnly.

2. **Never blank out a CRM field** because SigParser has no value for it.
`skipIfEmpty` defaults to TRUE: an empty source value is omitted from the
payload entirely. Only an explicit `"skipIfEmpty": false` lets SigParser
clear a field.

3. **Never create duplicates.** Every write path must end in an upsert or a
search-then-decide. Design it so that deleting all local state costs extra
API calls but can NEVER produce duplicate records. Matching order:
a. local cache of SigParser record_id → CRM record id (a direct update)
b. on a cache miss, one batched search per batch of records, looking up both
the key field AND the fallback match field (email / domain)
c. still nothing → upsert merging on the key field, so the CRM itself
resolves the race if the record appeared between our search and our write

4. **Resumable cursors.** Persist the SigParser `next_url` per object type to a
state file, plus `lastRunAt` and a hash of mapping.json. Write it atomically
(temp file + rename) and flush periodically mid-run, not just at the end, so
a crash doesn't cost the whole run.

5. **Change detection.** Cache a content hash of the last payload written per
record. If the newly mapped payload hashes identical, send nothing at all.
A nightly run over 100,000 unchanged contacts should cost ZERO writes.
Changing mapping.json must invalidate these hashes.

6. **Respect rate limits by design.** Serialise and pace outbound calls to stay
under the CRM's documented limit. Retry 429s honouring Retry-After, and 5xx
with exponential backoff. If a batch write fails, retry the records
individually so one bad record can't take down the other nine.

7. **Dates before 1900** (SigParser's `0001-01-01` "never" sentinel) must be
treated as empty, not written as a real date.

8. **Fail before you start, not halfway through.** At startup: verify the
SigParser key via /user/me, then validate every `target` in mapping.json
against the CRM's live schema and refuse to run if a field is missing,
read-only, or the wrong type. A wrong field name that fails mid-run leaves a
half-synced CRM.

9. **Sync companies before contacts**, so a contact's company record usually
already exists by the time you link to it.

Build these CLI commands:

```
sync Pull SigParser deltas and write them to the CRM (default)
--dry-run Read both APIs, write NOTHING, log every decision
--only contacts Contacts only
--only companies Companies only
--limit N Stop after N records per object type, WITHOUT advancing
the cursor — so nothing gets skipped on the next run.
This is the safe way to test against a real CRM.
check-mapping Verify every mapping.json target exists and is writable
create-fields Show which custom fields the CRM is missing
--apply Actually create them (if the CRM's API allows it)
sigparser-fields Print every SigParser contact and company field name
```

Also produce:

- `.env.example` with every variable and a comment explaining it. Secrets come
from `.env` only — never hardcoded, never committed. Add `.gitignore`.
- Structured logging to `logs/YYYY-MM-DD.log` AND stdout, one file per day,
auto-pruned after 30 days. Every skipped record must log its reason, e.g.
`[contact info@acme.com] skipped by value filter: role address (info@)`.
Do NOT log a line per unchanged record — count them in the summary instead.
Never log full request/response bodies on the success path; on failure, log
the error body truncated to ~800 characters.
- An end-of-run summary: created / updated / unchanged / skipped / failed per
object type, plus total API calls and duration. Exit 0 on success, non-zero
on a fatal error, so a scheduler can detect failure.
- A `README.md` covering: prerequisites, how to get both API keys and exactly
which scopes/permissions they need, setup, how to do a safe first test, how
to schedule it on Windows Task Scheduler / cron / a container, the full
mapping.json reference, how to add a new field, how to reset state, and a
troubleshooting table of the actual error messages I'm likely to see and what
each one means.

## Step 5 — Verify before you tell me it's done

- Make it typecheck / lint clean.
- Run the dry-run end to end against both real APIs and show me the log output.
- Walk me through exactly what to run for a first safe test against a sandbox:
dry run first, then `--limit 2`, then inspect those records by hand.
- Tell me honestly what you could not verify without writing to my CRM.

Start with Step 1.
````

---

## Features worth asking for

The prompt covers the essentials. Depending on your situation, these are worth adding — just append them to the prompt or ask for them in follow-up turns once the base app works.

**Sync behaviour**

- **Enrich-only mode** — update people already in the CRM, never create new ones. Very common for teams with a curated CRM.
- **Owner assignment** — set the CRM record owner from `relationships_strongest` (the teammate with the strongest relationship), mapped through a SigParser-email → CRM-user-id lookup table.
- **Two-way awareness** — read a "do not sync" checkbox in the CRM and skip those records.
- **Deal/opportunity context** — only sync contacts at companies with an open deal.
- **Activity timeline** — write meetings and email counts as CRM activity records rather than as fields.
- **Suppression list** — a `suppress.txt` of domains and addresses that never sync (competitors, personal contacts, your own staff).

**Data quality**

- **Deduplication report** — a command that reports likely duplicates in the CRM without changing anything.
- **Change log** — write a CSV of every field change so you can audit what the sync did last night.
- **Stale-record flagging** — set a field when `interactions_total_latest` is older than N days.
- **Confidence thresholds** — only write a job title if SigParser's confidence is above a bar.

**Operations**

- **Slack or email notification** on failure, or a daily summary.
- **Health-check endpoint** if you're running it as a service.
- **Docker image + compose file** if you're deploying to a container host.
- **GitHub Actions scheduled workflow** as a zero-infrastructure scheduler.
- **Metrics output** — write run stats as JSON for a dashboard.

**Safety**

- **Confirmation prompt** above a threshold — "this run will modify 4,812 records, continue?"
- **Backup-before-write** — snapshot the records about to change to a local file.
- **Allow-list mode** — during rollout, only write records whose email is in a test list.

---

## Running what it built

The exact commands depend on the language you chose, but the shape is the same.

**1. Install and configure**

```bash
npm install
cp .env.example .env
```

Edit `.env` and fill in your SigParser API key, your CRM credentials, and any account or instance identifier your CRM needs.

**2. Discover your field names**

```bash
npm run sigparser-fields
```

This prints every SigParser contact and company field, including custom fields on your tenant. Use it whenever you want to add a column to `mapping.json`.

**3. Check the mapping against your CRM**

```bash
npm run check-mapping
npm run create-fields # shows what's missing
npm run create-fields -- --apply
```

Do this after every `mapping.json` edit. A wrong field name is the single most common cause of a failed run.

**4. Dry run**

```bash
npm run dry-run
```

Nothing is written. Read the log carefully: it tells you which records would be created, which updated, and which were filtered out and why. **Do not skip this step.**

**5. Write a couple of real records**

```bash
npm run sync -- --limit 2
```

Open your CRM and look at those two records field by field. Because `--limit` doesn't advance the cursor, nothing gets skipped when you run the full sync later.

**6. Full backfill, then schedule it**

```bash
npm run build
npm start
```

The first run pulls your entire SigParser history, so it can take a while — plan for roughly the rate limit of your CRM divided by the batch size. Then schedule it:

- **Windows** — Task Scheduler, daily trigger, action `npm start`, with the working directory set to the project folder so the state file and logs resolve correctly.
- **macOS / Linux** — `0 2 * * * cd /opt/sigparser-crm && /usr/bin/npm start >> logs/cron.out 2>&1`
- **Container** — mount a volume at the state-file directory so the cursor survives restarts.

⚠️ **The state file must live on persistent storage.** If it's lost, every run re-fetches your entire SigParser history from the beginning. That's not dangerous — the upsert logic means you still won't get duplicates — but it's slow and burns API quota.

---

## Tips for working with the agent

**Let it ask questions.** The prompt deliberately stops twice to interview you. If your agent barrels ahead and starts writing code, stop it and say *"go back to Step 3 and ask me the questions first."*

**Point it at the sandbox.** Say so explicitly: *"the credentials in .env are for my HubSpot developer sandbox, not production."*

**Iterate on the mapping, not the code.** Once the app runs, adding a field should be: find the name with `sigparser-fields`, add a line to `mapping.json`, run `create-fields --apply`, run `check-mapping`, re-run. If the agent proposes a code change to add a field, tell it that's a bug in the mapping layer.

**Ask it to explain a record.** *"Why is jane@acme.com not in my CRM?"* — the logs are designed to answer exactly that, and the agent can grep them for you.

**Commit early.** `git init` and commit as soon as the dry run works, so you can always get back to a working version.

---

## Where this can go wrong

| Symptom | What's usually happening |
|---|---|
| Duplicate records appearing | The merge key field isn't being written, or something else in the CRM is overwriting it. Check the key field first. |
| Relationship columns are all empty | The plural/singular trap — `relationships_*` on contacts, `relationship_*` on companies. |
| Everything says "unchanged" but the CRM looks wrong | The payload hash cache is stale. Delete the record cache file and re-run. |
| Run dies partway with a field error | `check-mapping` wasn't run after a field was renamed in the CRM. |
| Rate limit errors | The pacing config is set too close to the CRM's documented limit. Leave headroom for anything else writing to the same account. |
| Fields a human typed got wiped | A field has `skipIfEmpty: false` when it shouldn't. |

---

## A note on what "vibe coded" means here

An AI agent will write you a working integration quickly. It will not, on its own, know that your ops team renamed a field last quarter or that three people in your CRM share an email address. Treat the generated app as a solid first draft written by a fast, capable engineer who has never seen your data:

- Always dry-run first
- Always test against a sandbox
- Read the filter logic and confirm you agree with what it's excluding
- Watch the first few scheduled runs before you stop watching

---

**Need help?** If you get stuck, reach out to support with your run log (redact the API keys) and the `mapping.json` you're using — those two files usually explain everything.
Did this answer your question?