Skip to main content

Use AI to build a vibe coded integration

# 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?