API reference·7 min read

How to connect Operelio to a custom CRM

Define your in-house CRM's record shape as a custom template, then automate cleanup and formatting over the API and feed the output to your CRM's own importer.

By Operelio team · Updated July 2026

On this page9
  1. 1.How the connection works
  2. 2.Define your CRM's shape once
  3. 3.Authenticate
  4. 4.The configJson for a custom template
  5. 5.The full pipeline, end to end
  6. 6.Run cleanup before formatting
  7. 7.Quotas and limits
  8. 8.What this setup does and doesn't do
  9. 9.Frequently asked questions

How the connection works

Plenty of companies run a CRM they built themselves. Operelio doesn't push records directly into a custom CRM (direct push covers HubSpot, Salesforce, and Pipedrive only), but it doesn't need to. Every custom CRM already has an import path: a CSV importer, a bulk endpoint, or a load script. Operelio's job is to make sure the file you feed that importer is clean, deduplicated, and shaped exactly the way your CRM expects, every time.

The pattern: define your CRM's record shape once as a custom template in the app, then automate everything else over the API. Upload a raw file, run cleanup jobs, run a CRM Formatter job that maps to your template, download the finished file, and hand it to your importer. The API is available on the Team and Agency plans.

Define your CRM's shape once

Start in the app, not the API. Open the CRM Formatter and on the Format step click Build a custom schema. Add one field per column your CRM's importer expects: a label, a type, and a required flag. Seven field types are available: Text, Email, Phone, Number, Date, URL, and Yes/No. Email and Phone fields get automatic cleanup (emails lowercased and trimmed, phone numbers reduced to digits plus a leading +); the other types pass through unchanged.

Save the template so it appears in your Load schema menu for dashboard runs. Custom templates start on the Pro plan (5 on Pro, 25 on Team, 75 on Agency), and since the API itself needs Team or Agency, your plan already includes them.

Authenticate

Generate a key on the API Keys page in the app and send it in the Authorization header of every request:

Authorization header
Authorization: Bearer op_your_api_key_here

New to the API? The Authentication and API quickstart guides cover keys and your first call. This guide assumes you have a key and focuses on the custom-CRM pipeline.

The configJson for a custom template

A CRM Formatter job is toolType "crm_formatter" with a configJson where three keys do the work. schemaName must be the string "__custom__". schema_fields carries your field definitions inline: an array of objects with key, label, type, and required. mappings connects your source columns to those fields: an object of source column name to field key. The type values are string, email, phone, number, date, url, and boolean.

Three more keys are optional. required_fields is an array of field keys to warn about when a row leaves them empty (defaults to none). output_order sets the column order of the output file (defaults to your schema_fields order). cleanup toggles the standard cleanup steps (trimming, email and phone standardization, name splitting, blank-row removal, deduplication); omit it and the dashboard defaults apply.

The fastest way to a known-good configJson is the discovery loop from the Endpoints and tools guide: run your custom template once in the dashboard with DevTools open on the Network tab, then copy the configJson from the POST /api/jobs request body. What the dashboard sends is exactly what the API accepts.

Formatting for a built-in CRM instead? Set schemaName to a provider_object key like hubspot_contacts or salesforce_leads and skip schema_fields entirely. Operelio loads the field definitions for you.

The full pipeline, end to end

Upload, format, poll, download. This example maps a raw export to a custom template with four fields.

Custom CRM pipeline
# 1. Upload the raw file
curl -X POST https://operelio.com/api/v1/uploads \
  -H "Authorization: Bearer op_your_api_key_here" \
  -F "file=@raw_contacts.csv"
# Response: { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "originalName": "raw_contacts.csv", "sizeBytes": 48210 }

# 2. Create a CRM Formatter job against your custom template
curl -X POST https://operelio.com/api/v1/jobs \
  -H "Authorization: Bearer op_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "inputFileId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "toolType": "crm_formatter",
    "configJson": {
      "schemaName": "__custom__",
      "schema_fields": [
        { "key": "email", "label": "Email", "type": "email", "required": true },
        { "key": "first_name", "label": "First name", "type": "string" },
        { "key": "last_name", "label": "Last name", "type": "string" },
        { "key": "account_ref", "label": "Account ref", "type": "string" }
      ],
      "required_fields": ["email"],
      "mappings": {
        "Email Address": "email",
        "First Name": "first_name",
        "Last Name": "last_name",
        "Customer Ref": "account_ref"
      }
    }
  }'
# Response: { "id": "f0e1d2c3-b4a5-4968-8776-5a4b3c2d1e0f", "toolType": "crm_formatter", "status": "queued", "createdAt": "..." }

# 3. Poll until completed (every 2 seconds is plenty)
curl https://operelio.com/api/v1/jobs/f0e1d2c3-b4a5-4968-8776-5a4b3c2d1e0f \
  -H "Authorization: Bearer op_your_api_key_here"
# When done: { "status": "completed", "outputFileId": "9c8b7a65-4d3e-42f1-a0b9-8c7d6e5f4a3b", "outputFileName": "raw_contacts_crm.csv", ... }

# 4. Download the formatted file
curl -O -J https://operelio.com/api/v1/files/9c8b7a65-4d3e-42f1-a0b9-8c7d6e5f4a3b/download \
  -H "Authorization: Bearer op_your_api_key_here"

# 5. Load the file with your CRM's own importer

Run cleanup before formatting

Formatting a messy file produces a well-shaped messy file. For recurring feeds, chain one or two cleanup jobs ahead of the formatter. Every completed job's outputFileId is a normal file in your workspace, so you pass it straight in as the inputFileId of the next job.

A typical chain for a contact feed: clean_headers first (so column names become predictable; "Email Address" becomes "email_address"), then deduplicate on the email column, then crm_formatter. Write your mappings against the cleaned header names, because that is what the formatter will see.

Chaining cleanup jobs
# Job 1: clean headers (no configuration needed)
curl -X POST https://operelio.com/api/v1/jobs \
  -H "Authorization: Bearer op_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "inputFileId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "toolType": "clean_headers", "configJson": {} }'
# Poll GET /jobs/:id until completed, then use its outputFileId below

# Job 2: deduplicate on the cleaned email column
curl -X POST https://operelio.com/api/v1/jobs \
  -H "Authorization: Bearer op_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "inputFileId": "file_from_job_1",
    "toolType": "deduplicate",
    "configJson": {
      "columns": ["email_address"],
      "keepStrategy": "first",
      "caseInsensitive": true,
      "trimWhitespace": true
    }
  }'

# Job 3: crm_formatter on job 2's outputFileId, exactly as in the pipeline above

Every job in the chain counts as one job against your monthly quota (750 jobs/month on Team, 2,000 on Agency), the same as running it in the dashboard.

Quotas and limits

Two API limits apply: a monthly cap on total calls (Team 1,000, Agency 5,000, counting every request including status polls) and a burst limit of 60 requests per minute per key. Poll no faster than every 2 seconds and a short cleanup-then-format chain stays well inside both.

Every job in the chain also counts against your monthly job quota, the same as a dashboard run. For handling a 429 or a malformed-request error cleanly, see the Rate limits and Error handling guides.

What this setup does and doesn't do

Operelio prepares the file; your CRM's importer loads it. Direct push, where Operelio writes records into the CRM itself, exists only for HubSpot, Salesforce, and Pipedrive, and a custom template always produces a downloadable file rather than a push. That split is deliberate: your importer already knows your CRM's rules for matching, permissions, and side effects, and a clean, consistently shaped CSV is the safest thing to hand it.

There is no webhook on job completion, so the poll step is part of the pipeline. Most jobs finish in under 10 seconds, so a 2-second poll interval keeps the wait short.

Frequently asked questions

Can Operelio push records directly into my custom CRM?

No. Direct push is available for HubSpot, Salesforce, and Pipedrive only. A custom template always produces a formatted file that you download and load through your CRM's own import path, which keeps your CRM's own matching and permission rules in charge of the final write.

Do I have to send schema_fields on every job?

Yes. Over the API, a custom template travels inline: schemaName "__custom__" plus the schema_fields array in every job's configJson. Templates you saved in the app are a dashboard convenience and can't be referenced by name from the API. The upside is that your field list lives in version control next to the script that uses it.

What plan do I need?

Team or Agency, since the pipeline runs on the API. Team includes 750 jobs/month, 1,000 API calls/month, and 25 custom templates; Agency raises those to 2,000 jobs, 5,000 calls, and 75 templates.

What happens to columns I don't map?

They're dropped from the output by default, matching the dashboard's Remove unmapped columns toggle. To carry them through, pass cleanup: { "removeUnmappedColumns": false } in configJson and the unmapped source columns are kept alongside the mapped ones.

My CRM added a field. How do I keep the pipeline in sync?

Add the field to schema_fields and its source column to mappings in your script. If you also use the template for dashboard runs, update it in the app too; the app template and your script don't sync automatically, and the API job always uses exactly what you send.

Ready to get started?

Upload a file and run your first transformation. Free, no credit card required.