Keybe/Developers/CRM connection protocol

CRM connection protocol

How BIKY.ai connects with your CRM or DMS: methods, field mapping, sync and duplicates, what we need from you, errors and lessons learned.

BIKY.ai connects with each brand's or distributor's CRM or DMS. It delivers every lead that arrives through any channel, reports progress (in real time or when the conversation closes, depending on the CRM), and receives the changes the advisor makes. Everything revolves around the SalesCard, the BIKY.ai record that stores the lead, its step within the Funnel, and the id the CRM assigns to it. This protocol captures what we learned from more than fifteen integrations across Latin America.

How we connect

There are three paths, and a CRM can combine them: your CRM notifies us via webhook, BIKY.ai queries its API, and progress updates go out through that same API. Every id the CRM returns is stored in the SalesCard or on the contact, so the next event updates the record instead of duplicating it.

Your CRM defines the authentication: OAuth2 client_credentials with a connected app, OAuth2 authorized by an admin user, a short-lived token, an API key or Basic Auth. Incoming webhooks use a unique URL with a token per flow. You can also enable body encryption, and Flows verifies the HMAC signature from platforms that send one. Web forms and lead providers also come in this way. Meta forms arrive as one more channel.

There are three variants. Some integrations run on an integration platform (iPaaS), with OAuth2 connectors authorized by one of the client's admin users. When the CRM has no API, or the API doesn't cover a process, a robot (RPA) operates its web portal with users assigned by the client. And if your system isn't in this document, BIKY.ai can send every SalesCard change to the webhook you give us.

It also works the other way around: your CRM can call the BIKY.ai API with your account key. This lets it create or look up contacts (by phone, email or id) and create, query or move SalesCards between the steps of a Funnel. Examples are in the Keybe Postman collections. The fields, allowed values and examples for that API are in the API Fields and Structure section.

CRM integrations

Everything travels over HTTPS. Three things change from one CRM to another: who initiates the call, how it authenticates, and which identifier prevents duplicates. These are the integrations with the longest track record.

Sale U

  • Connection and authentication: inbound via webhook to a unique URL with a token. Outbound via REST API with an API key, plus a secret and a timestamp in the body.
  • What data travels: inbound, the assigned lead: name, phone, email, dealership, model and prospect id. Outbound, every activity: status, appointment, follow-up note, lock or release, temperature and close.
  • Direction and frequency: real time. Depending on the dealership, both directions or only from BIKY.ai to Sale U. Each dealership needs the provider's authorization.
  • How we prevent duplicates: the contact is looked up by phone and the SalesCard by the prospect id. If Sale U retries the same lead, the existing one is updated.

Salesforce

  • Connection and authentication: REST API (standard objects or the customer's Apex), sometimes behind their integration layer. OAuth2 client_credentials with a connected app, OAuth2 from an admin user, or Basic Auth.
  • What data travels: outbound, the lead with contact, model, source, dealership and sales advisor; then the status and the appointment. Inbound, the won or invoiced opportunity.
  • Direction and frequency: outbound when the conversation closes, when an appointment is scheduled and on every status change. Inbound via a periodic SOQL query (for example, every hour), filtered by last modified date.
  • How we prevent duplicates: the lead id is stored on the contact and subsequent sends update it. If the CRM also deduplicates on its side, its rule is respected.

Seekop / SICOP

  • Connection and authentication: robot (RPA) on the portal with each dealership's user. If the dealership has API access, REST API in ADF format with an API key.
  • What data travels: inbound, the leads from the CRM's waiting queue. Outbound, the follow-up on the lead record; via API, the prospect (contact, model, source and comment) with its dealership, and the appointment.
  • Direction and frequency: with the robot, both directions: it reads the queue every few minutes and logs the follow-up on the lead record. Via API, from BIKY.ai to Seekop when the conversation closes and when an appointment is scheduled.
  • How we prevent duplicates: the robot uses the lead id in the CRM and an idempotency key per action. Via API, the contact is flagged as sent.

HubSpot

  • Connection and authentication: Flows connector with OAuth2 authorized by a customer user; in some cases, webhook or iPaaS.
  • What data travels: outbound, the contact and, depending on the case, a deal in an agreed pipeline. Inbound sync of deals that reach the won stage has been designed.
  • Direction and frequency: outbound when the conversation closes or on each purchase. Inbound via periodic polling has been designed.
  • How we prevent duplicates: contact upsert. Storing its id in BIKY.ai and matching on that id has been designed.

Microsoft Dynamics 365

  • Connection and authentication: OData API that the customer or their integrator publishes behind an API gateway, with a subscription key in the header.
  • What data travels: outbound, the lead with its campaign; the appointment is created and updated.
  • Direction and frequency: from BIKY.ai to Dynamics, when the conversation closes and on every appointment event.
  • How we prevent duplicates: only new leads are sent, not edits. The appointment id is stored on the contact and changes are applied to it.

Manufacturer CRM

  • Connection and authentication: JSON REST API with a short-lived token; multipart with a PDF for the order. No webhooks: a dedicated service polls the API. Whatever the API doesn't cover is handled by a robot on the portal.
  • What data travels: inbound, leads and their status changes, per dealership. Outbound, creation of our own leads, follow-up, the test drive and the pre-sale order.
  • Direction and frequency: inbound via polling every minute, respecting a minimum interval between polls for the same dealer; outbound on every SalesCard event.
  • How we prevent duplicates: the lead code is stored on the SalesCard; lead creation uses the SalesCard id as the idempotency key.

Other integrations we have built

Some no longer have active accounts, but they can be implemented again.

SystemTypeConnection and data
TecnomAutomotive CRMLead in ADF format via POST to its web connector, with username and password.
Syonet 360Automotive CRMThe CRM sends leads via webhook; BIKY.ai logs calls, visits and appointments via REST API with Basic Auth.
IntelisisDMSThe DMS consumes the BIKY.ai API and reads contacts and SalesCards by their id.
Total DealerService DMSAppointments API.
UpnifyCRMREST API for prospects and appointments, with a token that can only create records.
OdooCRM and ERPContact and opportunity creation with their UTMs.
Zoho CRMCRMConnector with OAuth2 and token refresh; Zoho notifies via webhook.
RD Station CRMCRMFlows connector: creates deals and moves them between the stages of its funnel.
SmarthomeCRMRead and write REST API.
ComunikeCRMWebhook.
SICOReal estate CRMOutbound via REST API, with a username and password that issue a one-hour token.
WasiReal estate CRMSyncs the property inventory with the BIKY.ai catalog.

Flows also includes nodes for the Siigo, Alegra and Defontana ERPs, and for a service DMS (appointment scheduling). Online stores (Shopify, VTEX and WooCommerce) sync catalog and contacts with BIKY.ai and send notifications for abandoned carts, payments and order changes. Their connectors, like those for payments and carriers, are on the integrations page.

Data mapping

The mapping is agreed field by field before building, and each closed list (sources, models, dealerships, statuses) gets its own equivalence table.

BIKY.ai dataTypical CRM fieldMapping rule
First and last nameFirst name, last nameSome sources send the full name in the last name field: it is split before sending. If the CRM requires a last name and BIKY.ai does not have one, what to send is agreed in advance.
Mobile phoneMobileWith country code (52 plus 10 digits in Mexico), unless the CRM or the object requires the national number. If it arrives split across several fields, how it is assembled is agreed in advance. The country code is filled in only if the source guarantees it.
EmailEmailOptional, unless the object requires it. If the CRM validates the format, an empty field is omitted; some CRMs reject accents or ñ.
Channel and campaign (UTM)Source, campaignTranslated to the CRM's source catalog; anything without an equivalent goes to "Other." Each value goes in its own field, without concatenating.
Product or model of interestModel, trimEquivalence table, with the CRM code stored on the product: matching by name fails. If the field is required, a default value is agreed.
DealershipDealer or branch codeDealership → code table, validated against the dealerships configured in BIKY.ai, not against the trade name.
Assigned sales advisorOwner or advisorThe advisor's ID in the CRM, loaded into BIKY.ai. If the CRM assigns with its own rules, it is not sent.
Funnel stepStatus or stageTwo-way equivalence table (example below).
Appointment or test driveAppointment, eventIn the exact time zone and format the CRM requires, with start and end. The ID of each appointment is stored so it can be updated.
Record ID in the CRMNot applicableStored on the contact or on the SalesCard, and it is the key for every update.
Length of each fieldNot applicableThe maximum length of each field is confirmed before building: a longer ID, text or phone number is rejected.

Statuses are mapped against the Funnel steps. In an automotive account, the Sales Funnel comes with these nine. The display name of each step may vary by account; the key does not change, and it is the one the API uses.

Sales Funnel stepWho moves the SalesCardHow it is reflected in the CRM
No Response ($step_mql$)The SalesCard is created here when the lead arrives, through any channel or from the CRM.New lead, unassigned or assigned.
Responded MQL ($step_responded_mql$)BIKY.ai, when the customer replies.First contact logged as follow-up.
Interested (SQL) ($step_sql$)BIKY, when the customer qualifies; or the CRM, if it marks the lead as in follow-up.In follow-up.
Scheduled ($step_scheduling$)BIKY or the advisor, when the appointment is booked.Appointment or event created.
Show ($step_show$)BIKY. One hour after the appointment, it sends a WhatsApp template to the customer and the advisor asking whether the customer attended, and moves the SalesCard based on the reply.Visit logged.
Demo ($step_test_drive$)BIKY, when the reply to that template indicates a test drive took place.Test drive logged.
Pre-sale ($step_won$)The advisor, with product, amount and date; or the CRM, if it reports the opportunity as won.Remains in follow-up; with the manufacturer's CRM, the pre-sale order is sent.
Invoiced ($step_billed$)The advisor. With the manufacturer's CRM, the CRM when it marks the sale.Sale closed. With the manufacturer's CRM, BIKY.ai does not send it, because it is irreversible there.
Lost ($step_lost$)The advisor, with the reason; or the CRM, when it marks the lead as lost.Lost, with reason.

A lead that the CRM marks as invalid does not move the SalesCard: only the status is stored, because it is not a lost sale. Automotive accounts also include an After-sales Funnel (qualified, received, diagnosis, approval, in service, delivery, payment, follow-up and not approved), available to be mapped to a service shop DMS when needed.

Synchronization and duplicates

Outbound data travels in real time or when the conversation closes, depending on the CRM. Inbound data arrives by webhook if the CRM offers one and, if not, by periodic polling, anywhere from every minute to every hour. No CRM integration uses SFTP or automatic file exchange.

What triggers a push to the CRM:

  • A SalesCard is created: the lead is created with contact, source, model and dealership.
  • A contact from the advisor or from BIKY (call, email or WhatsApp): a follow-up record.
  • A step change in the Funnel: the new status.
  • An appointment is scheduled, moved or canceled: the appointment or the test drive.
  • A conversation ends: the lead with its complete data and its status.

Historical data is loaded only once from the CRM's API, without triggering messages or flows, and with each lead's original date.

Rules against duplicates:

  1. Every id the CRM returns is stored in BIKY.ai, and the next event updates that record instead of creating a new one.
  2. Inbound data must be idempotent: the same lead id or the same repeated event updates the existing SalesCard. CRMs retry, and a retry must not open another SalesCard.
  3. Before a contact is created, it is looked up by normalized phone number. If the CRM deduplicates on its own, its rule is respected and the id it returns is stored.
  4. A person with an open SalesCard in the Sales Funnel does not open another one. A different interest does, if the CRM records it as a separate lead.
  5. The lead enters through a single path. If a form feeds both BIKY.ai and the CRM at the same time, the same lead is created twice.
  6. Confirm with the client whether their CRM creates a new record on every push. Until there is an answer, only new records are sent, not edits.

What we need and how long it takes

Building is usually the fast part. Inputs and approvals set the timeline. In our 2025 and 2026 projects, half of the dealerships that joined an already connected CRM were ready in less than two weeks from the request. A new integration took a median of five weeks, not counting the commercial stage.

What stretches a project the most: credentials that don't work, a sandbox that differs from production, catalogs that arrive late, the nondisclosure agreement, approval from the brand or the CRM provider, and the WhatsApp line for each dealership. Here is the list, by phase.

0. Before we start

  • Nondisclosure agreement and any legal documents your company requires.
  • Security review, if applicable: we hold ISO/IEC 27001 certification.
  • Approval from the brand or the CRM provider for each dealership, if required.

1. Discovery

  • Current API documentation: objects, required fields, allowed values for each list (picklist), error codes and rate limits.
  • Whether the CRM sends notifications via webhook or has to be polled.
  • Which objects are synced (lead, contact, opportunity, appointment) and who owns each field.
  • Whether your CRM creates a new record on each submission or updates the existing one.
  • Whether your CRM also captures leads through its own forms, and which of the two takes precedence.
  • A technical lead on the client side or from their integrator.

2. Mapping

  • Catalog of sources and statuses with their codes, and which statuses are terminal.
  • List of models or products with their code, and the default value if the field is required.
  • Dealerships or branches with their code in the CRM.
  • Advisor IDs, if the CRM needs to know who is handling the lead.
  • The maximum length and format of each field, including the phone number.
  • The CRM's own rules: deduplication, validations and automatic assignment.

3. Test environment

  • Sandbox with current credentials and the same version, the same reference data and the same dealerships as production.
  • Integration credential, one per environment, with permissions on the objects we will write to: connected app (OAuth2 client_credentials) or technical user.
  • Agreed test leads: a test in production creates real records in your CRM.

4. Go-live and monitoring

  • Production credentials through the secure channel we agree on, never by email, chat or tickets.
  • Advance notice before rotating or deactivating a credential.
  • The WhatsApp line for each dealership, the IDs of its advisors and the rollout schedule by dealership group.
  • A contact for incidents.

Common Errors and Monitoring

Almost all rejections come from the data, not the network: an empty required field, a format the CRM doesn't accept, or a CRM-specific rule. These are not retried, because they would fail the same way again. Instead, we fix the data or the rule and resend the event.

ErrorTypical causeWhat we do
The CRM responds 200 but rejectsThe reason comes in the response body; other CRMs respond 201 to a duplicate.The body must be validated, not just the HTTP code. Flows marks a 200 with success: false as a failure; if the CRM uses a different error indicator, it is validated within the flow itself.
Empty required fieldThe lead arrives without a model of interest, product, or email.Validations before sending and an agreed default value. A rule that is too strict, such as not sending without an email, can block all leads.
Field longer than allowedAn id, a text, or a phone number that exceeds the CRM field.The length of each field is confirmed in the mapping.
New CRM validationThe CRM starts requiring a field or a format without notice.When rejections increase, we check what changed in the CRM.
Dealership without a codeA lead arrives from a dealership that is not in the equivalence table.The table is validated before turning on each dealership.
Invalid phone numberDuplicated country code, an extra 1, or a truncated number.Normalization before sending; anything unrecoverable is flagged as invalid and is not sent.
Date or time rejectedTime zone, format, or a test drive marked "completed" with a future time.The exact time zone and format the CRM requires, validated before sending.
Expired or rotated credentialExpired token, or a credential deactivated during a rotation.The token is renewed before it expires using its refresh token, or requested again.
Too many requests (429)Periodic queries exceed the CRM's limit.A minimum interval between queries per distributor.
CRM down or slow5xx or timeout.Dedicated services and the robot retry with increasing backoff. In Flows, retries are enabled per flow, because if the CRM is not idempotent, retries can create duplicates.
CRM-specific ruleA client validation, such as not allowing the owner to be reassigned.The submission is adjusted to the rule, for example by letting the CRM assign the owner.

How we monitor it:

  • Every execution is recorded in the history, with the data sent in the CRM nodes.
  • In integrations with a dedicated service and in the robot, a monitor checks every 5 minutes. It classifies each failure by cause, displays it on a dashboard, and sends email alerts only for critical and recurring issues.
  • We closely follow each dealership for the first few days after turning it on. After that, whenever there is a complaint, we reconcile the CRM leads against the SalesCards and BIKY.ai conversations.

Lessons learned

Almost everything that failed comes down to four rules: store the external id, validate the body of every response, confirm fields and catalogs before building, and test each dealership with a test lead before turning it on.

  • Sale U. The same lead arrived many times, and each resend opened another SalesCard.
    • What we changed: we cleaned up the duplicate SalesCards, and the inbound flow now looks up the SalesCard by the prospect id before creating one.
    • What we would do differently: link the SalesCard to the prospect id.
  • Sale U. The CRM started validating fields it used to accept, and older flows failed with no changes on our side.
    • What we changed: we fixed the fields in the affected flows.
    • What we would do differently: send an alert when an integration's rejections go up.
  • Sale U. New dealerships had no assignment rule, so their leads went back to the CRM unattended.
    • What we changed: a review of the dealership table whenever a new account is set up.
    • What we would do differently: test each dealership with a test lead before turning it on.
  • Salesforce. Many leads were rejected because of an empty required field: the model of interest.
    • What we changed: we requested the CRM's full documentation: required fields, allowed values for each list (picklist), and catalogs.
    • What we would do differently: agree on default values before turning it on.
  • Salesforce. A CRM rule blocked owner reassignment, and the rejection was not visible in BIKY.ai.
    • What we changed: we found the cause in the connector logs.
    • What we would do differently: log every rejection and ask about validation rules during discovery.
  • Salesforce. The won-sales query filtered by creation date, so almost nothing came through. It also moved SalesCards from the After-sales Funnel to the won step ($step_won$) of the Sales Funnel.
    • What we changed: the query now uses the opportunity's last modified date and filters by the Sales Funnel.
    • What we would do differently: test the query against six months of history before turning it on.
  • Salesforce. Meta forms fed both BIKY.ai and the CRM at the same time, so each lead was created twice.
    • What we changed: leads now come in only through the CRM.
    • What we would do differently: define a single entry path during discovery.
  • Seekop. When one account was split into per-dealership accounts, routing by dealership name stopped matching and some leads were never sent.
    • What we changed: a single shared template with each dealership's data in environment variables, and the previous integration shut off in the same step.
    • What we would do differently: never route by names the client can change.
  • HubSpot. Won-deal matching caught only a few cases: the HubSpot id was not stored on the contact, and email lookups produced false positives.
    • What we changed: we designed the matching to use the id only, with the exact phone number as a fallback.
    • What we would do differently: store the external id from the very first send.
  • Zoho. Access expired every hour because the token was not being refreshed.
    • What we changed: we switched to a connector that refreshes the token on its own.
    • What we would do differently: always use the refresh token or a connector that handles it.
  • Dynamics 365. The subscription key was sent in the URL, but the gateway requires it in the header. On top of that, a CRM field was shorter than our id.
    • What we changed: the key now goes in the header, and the client lengthened the field.
    • What we would do differently: review headers and field lengths during mapping.
  • Several. Some CRMs require data that BIKY.ai does not always have, such as a phone number with an exact number of digits, the last name, or the product. One rule like that ended up blocking every lead for a client.
    • What we changed: we ask for those rules during discovery.
    • What we would do differently: measure with real data how many leads meet each rule before activating it.
  • Manufacturer CRM. Marking a sale as closed freezes the lead in the CRM permanently.
    • What we changed: BIKY.ai never sends that status. It only reads it.
    • What we would do differently: ask during discovery which statuses are irreversible.
  • Manufacturer CRM. A change in the CRM credentials cut off incoming leads.
    • What we changed: an alert when queries fail repeatedly over time.
    • What we would do differently: agree on advance notice of every change and delivery of new credentials through a secure channel.
  • Manufacturer CRM. The sandbox did not have the project distributor's data, and it accepted values that production rejects.
    • What we changed: we validate every field in the sandbox and confirm it in production.
    • What we would do differently: request a sandbox identical to production from the start.
  • Several. Phone numbers with a duplicated country code, an extra 1, or no country code created invalid contacts and duplicate SalesCards.
    • What we changed: phone normalization in the inbound flows, with rules per source.
    • What we would do differently: agree on the phone format during mapping, object by object.

API Fields and Structure

Fields, allowed values, and rules of the BIKY.ai API for contacts and SalesCards.

Access

Every call carries the account key in the Authorization: Bearer <key> header. Keybe issues one key per account and delivers it with the base URLs for the Contacts and Funnels services.

  • {app}: the BIKY.ai account id, one per brand or dealership. It goes in every path except the phone validation path.
  • {typeEntity}: people, business, or office. For leads, use people.
  • A single resource comes back as a plain object. A list comes back as {data, paginator}. paginator includes totalDocs, limit, page, totalPages, pagingCounter, hasPrevPage, hasNextPage, prevPage, and nextPage.
  • Errors come back as {statusCode, timestamp, path, message, error}. In a validation error, message is the list of fields that failed, for example ["responsible should not be empty"].

Contact and SalesCard

The contact is the person. The SalesCard is each sales opportunity for that person within a Funnel. A CRM usually maps its lead to both.

ContactSalesCard
What it isThe person or company, in the BIKY.ai CDPAn opportunity, placed in a step of a Funnel
How manyOne per person; looked up by phone or emailSeveral per person. While a SalesCard is open in the Sales Funnel, no other one is opened, unless the CRM registers a different lead
Its iduuid_id
How they are linkedNot applicablepersons[] holds the contact's uuid
Your CRM idexternalIds[], with systemName and valueutm.sourceCardId or an extendedData field

Endpoints

ResourceEndpointWhat it does
ContactsPOST /{app}/contactsCreates a contact.
ContactsPOST /{app}/contacts/upsert-or-fallbackCreates the contact or updates the one that already has that phone or email. Responds with {status, contact}, where status is created, updated, or fallback. With invalid data, it returns the account's fallback contact and the reason in reason.
ContactsGET /{app}/contacts/{typeEntity}/phone/{phone}Looks up by phone.
ContactsGET /{app}/contacts/{typeEntity}/email/{email}Looks up by email.
ContactsGET /{app}/contacts/{typeEntity}/{uuid}Reads a contact.
ContactsPATCH /{app}/contacts/{uuid}Updates a contact; typeEntity goes in the body.
ContactsPOST /utils/validate-phoneValidates a phone with the body {phone}.
FunnelsGET /{app}/funnel/with-stepsThe account's Funnels with their steps.
FunnelsGET /{app}/{funnelId}/stepsSteps of a Funnel, with their key (name), display name (label), and order (order).
SalesCardsPOST /{app}/funnel/{funnel}/steps/{step}/cardsCreates a SalesCard in a step.
SalesCardsPOST /{app}/funnel/{funnel}/steps/{step}/cards/upsertIf the same person has a SalesCard in that step created within the withinHours window (1 hour by default), it updates it; otherwise, it creates a new one.
SalesCardsGET /{app}/cards-filter?person={uuid}A contact's SalesCards, from newest to oldest.
SalesCardsGET /{app}/simple/cards/{id}Reads a SalesCard.
SalesCardsPATCH /{app}/simple/cards/{id}Updates a SalesCard or moves it to another step with stepName.
SalesCardsDELETE /{app}/funnel/{funnel}/steps/{step}/cards/{id}Deletes a SalesCard; the deletion is a soft delete.

Contact Fields

FieldTypeRequiredMeaning and values
typeEntitytextYespeople, business, or office.
nametextYesFirst name.
surnametextNoLast name.
phone[]listNoEach phone carries value with country code (525500001234), countryCode (52), country (MX), isPrimary, and verify. On creation, value, countryCode, and isPrimary are required; on a PATCH, verify is also required.
email[]listNoEach email carries value, isPrimary, and verify. On creation, value and isPrimary are required; on a PATCH, verify is also required.
registerChannel[]listYeschannel indicates where the contact came from (api, form, import, web, chatWeb, whatsapp, instagram, facebook, tiktok, email, phone, sms, salesforce, hubspot, rdStation, shopify, vtex, other, and others). data is free text with details about the source.
sourceobjectNoAttribution: utmSource, utmMedium, utmCampaign, utmTerm, utmContent, utmPartner, utmKam, utmCloser, channel, and channelOther.
externalIds[]listNoThe contact's ids in other systems, each as {systemName, value}. On a PATCH, the whole list is replaced, so we recommend using it together with isPushData.
extendedData[]listNoAccount-specific fields: category and field are required, plus value, valueArray, or valueMultiple.
documentType, documenttextNoDocument type and number (passport, nit, curp, rut, dni, and others).
responsibleobjectNoAdvisor who owns the contact: uuid and name required, email and phone optional.
country, province, city, zonetextNoLocation.
habeasDataobjectNoConsent: habeasDataStatus (Confirmed, Unconfirmed, OnlyLegals, or Blacklist), acceptance per channel, date, IP, country, and evidence.
tagslist of textNoFree-form tags.
isPushDatabooleanNoPATCH only. With true, the phones and emails sent are added to the existing ones; with false, they replace them.

SalesCard Fields

FieldTypeRequiredMeaning and values
nametextYesSalesCard title, usually the lead's name.
persons[]list of textYesThe contact's uuid.
responsibleobjectYesAdvisor or BIKY in charge: uuid and name required, email and phone optional.
descriptiontextNoFree text visible to the advisor.
amountnumberNoOpportunity amount. If it arrives as text, it is stored as a number.
productsData[]listNoProduct of interest: id, sku, and name required; price, down payment, financed amount, brand, and options.
utmobjectNoOpportunity attribution, with sourceCardId, the opportunity's id in your CRM. sourceHistory stores every source the SalesCard receives through cards/upsert.
extendedData[]listNoAccount-specific fields.
reasontextNoClosing reason, when the opportunity is won or lost.
prioritytextNoemergency, lowest, low, medium, high, or highest.
scheduleobjectNoAppointment: dataTime, advisor, status, and meetingUUID required; type, location, description, and entityUUID optional.
invoiceDataobjectNoInvoice: document, car (vin, brand, model, trim, year, and price), and customer.
stepNametextNoPATCH only. Key of the target step, for example $step_sql$.
isPushDatabooleanNoPATCH only. With true, extendedData is merged by field and productsData by id; without it, both are fully replaced.

Examples

Values between < > are provided by each account: Funnel, step, advisor, and product ids.

1. Create or update the contact. POST /{app}/contacts/upsert-or-fallback. The response is {status, contact}; contact.uuid is the id used in the SalesCard. With fallback, the reason comes in reason, and you should not create the SalesCard until the data is fixed.

{
  "typeEntity": "people",
  "name": "Mariana",
  "surname": "Torres",
  "phone": [{ "value": "525500001234", "countryCode": "52", "country": "MX", "isPrimary": true }],
  "email": [{ "value": "[email protected]", "isPrimary": true }],
  "registerChannel": [{ "channel": "api", "data": "MiCRM" }],
  "source": { "utmSource": "micrm", "utmCampaign": "lanzamiento-octubre" },
  "externalIds": [{ "systemName": "MiCRM_LeadId", "value": "L-000123" }]
}

2. Create the SalesCard in the first step. POST /{app}/funnel/{funnel}/steps/{step}/cards. The response returns the SalesCard with its _id.

{
  "name": "Mariana Torres",
  "persons": ["<contact uuid>"],
  "responsible": { "uuid": "<advisor uuid>", "name": "<advisor name>" },
  "description": "Lead received from MiCRM.",
  "productsData": [{ "id": "<product id>", "sku": "<sku>", "name": "<model>", "price": 450000 }],
  "utm": { "source": "micrm", "campaign": "lanzamiento-octubre", "sourceCardId": "OPP-000456" }
}

3. Step change. PATCH /{app}/simple/cards/{id}. Moves the SalesCard to Interested (SQL) without touching anything else.

{ "stepName": "$step_sql$" }

API Rules

CaseWhat the API doesWhat to do
Mexican phone on creation52 + 10 digits, 521 + 10, and +52 55 1234 5678 with spaces are stored as 52 + 10. With 10 digits and no country code, upsert-or-fallback responds with status: fallback and the fallback contact.Send the phone with the country code. With status: fallback, do not create the SalesCard: fix the data and resend.
Phone lookup52, 521, and +52 find the same contact; 10 digits without a country code return 404.Always search with the country code.
Existing contactupsert-or-fallback with an existing phone updates that contact and responds with status: updated.Use it so that creation from the CRM does not duplicate contacts.
Phones and emails in a PATCHWithout verify, it returns 400. With isPushData: true, they are added; with false, they replace the existing ones.Send verify: false if they are not verified.
source and externalIds in a PATCHBoth are fully replaced; externalIds: [] deletes them.Send the full externalIds list and write source only on creation.
SalesCard creation without responsible400 responsible should not be empty.Always send the advisor, or BIKY if there is no advisor.
productsDataEmpty id, sku, or name: 400. price as text: 400.If the product is not in the catalog, create the SalesCard without productsData.
SalesCard PATCHutm is merged. Without isPushData, extendedData and productsData are fully replaced. stepName moves the SalesCard.Use isPushData: true to add without deleting.
cards/upsertIf the person already has a SalesCard in that step created within withinHours, it updates it.Use it when the CRM may retry the submission.
cards-filterPagination starts at offset=1; search does not filter on this endpoint.Filter by person and compare utm.sourceCardId.
SalesCard deletionThe deletion is a soft delete: the SalesCard drops out of cards-filter, even though its GET by id still responds.Confirm the deletion with cards-filter.