Solutions Engineering · Per-Tool CRM Migration Reference

CRM → DevRev: Per-Tool Object & Field Mapping for AirSync Replacement

Date 2026-08-03 · Status: reference · Scope: Sales CRM · One section per source tool

Each CRM broken out separately — every standard object as its own table with that tool's real API column names, mapped to a DevRev target. Map to a DevRev standard first. Only tag CUSTOM when no standard exists. Where a clean replacement genuinely requires a DevRev standard object/field that doesn't exist today, it's flagged ⚑ STD-NEEDED — a gap to raise with product, not something to bury in custom.

NATIVE maps to an existing DevRev standard field/object CUSTOM model as custom field / custom object ⚑ STD-NEEDED clean replacement needs a DevRev standard that doesn't exist — flag, don't force to custom
Verify before you promise. DevRev CRM-side objects (Account, Contact, Opportunity, Lead) are gated per-org / per-plan. Field-exact picklists must be pulled live via describeSObject() (SFDC), Fields API (Zoho), Properties API (HubSpot), Get-Metadata (LeadSquared) against an authenticated org — vendor doc portals are JS SPAs and don't expose field tables to fetch.

DevRev identity model (applies to every tool)

Mapping the wrong identity object is the #1 migration defect.

Salesforce SObject API · describeSObject()

Field names = SFDC SOAP/REST API names. Id → external_ref/external_uid; SystemModstamp → delta cursor on every object.

Account SObject: Account

API columnDevRev targetTagNotes
Idexternal_refNATIVE+ paired Rev Org external_ref
Namedisplay_nameNATIVE
Websitedomain / websiteNATIVEdrives auto-linking
Industrycustom.industryCUSTOMenum; pre-seed picklist
AnnualRevenuecustom.annual_revenueCUSTOMminor units
NumberOfEmployeescustom.employee_countCUSTOM
Phonecustom.phoneCUSTOM
BillingStreet / BillingCity / BillingState / BillingPostalCode / BillingCountrycustom.billing_street / _city / _state / _zip / _countryCUSTOMno compound-address type — flatten to 5 fields
ShippingStreetShippingCountrycustom.shipping_street / _city / _state / _zip / _countryCUSTOMseparate prefixed set
Typetier⚑ STD-NEEDEDAccount-tier/segment standard field, if not enabled
ParentIdcustom.parent_accountCUSTOMself-ref; load parents first
OwnerIdowned_byNATIVEresolve → devu
SystemModstampaudit fieldsNATIVEdelta cursor

Contact SObject: Contact

API columnDevRev targetTagNotes
Idexternal_refNATIVE
FirstName/LastNamedisplay_name / full_nameNATIVE
EmailemailNATIVEdedup/merge key
Phone/MobilePhonephone_numbers[]NATIVE
Titlecustom.job_titleCUSTOM
AccountIdrev_org / accountNATIVEmust resolve, else orphan revu
OwnerIdowned_byNATIVE→ devu
LeadSourcecustom.lead_sourceCUSTOM
HasOptedOutOfEmailcustom.email_opt_outCUSTOM
MailingStreet / MailingCity / MailingState / MailingPostalCode / MailingCountrycustom.mailing_street / _city / _state / _zip / _countryCUSTOMflatten to 5 fields
SystemModstampaudit fieldsNATIVE

Lead SObject: Lead

API columnDevRev targetTagNotes
Idexternal_uid⚑ STD-NEEDEDnative Lead object gated; else custom object
Namedisplay_nameNATIVE
Companycustom.companyCUSTOMtext until converted (no Account yet)
Email/Phonecustom.email / custom.phoneCUSTOM
StatusstageNATIVEbuild stage diagram; zero-pad enum prefixes
LeadSourcecustom.lead_sourceCUSTOM
Ratingcustom.scoreCUSTOM
OwnerIdowned_byNATIVE→ devu
IsConvertedcustom.is_converted (bool)CUSTOM
ConvertedAccountId / ConvertedContactId / ConvertedOpportunityIdcustom.converted_account / _contact / _opportunityCUSTOMeach stores target external_ref
SystemModstampaudit fieldsNATIVE

Opportunity SObject: Opportunity + OpportunityLineItem

API columnDevRev targetTagNotes
Idexternal_uid⚑ STD-NEEDEDnative Opportunity gated; else custom object
Namedisplay_nameNATIVE
AccountIdcustom.account (ref)NATIVEresolve to Account
Amountcustom.amount (int)CUSTOMminor units
CurrencyIsoCodecustom.currency (enum)CUSTOMmulti-currency orgs
StageNamestageNATIVEmap pipeline 1:1; deprecate dropped stages
Probabilitycustom.probabilityCUSTOM
CloseDatecustom.close_date (ts)CUSTOMoptionalTs (empty ts breaks loader)
ForecastCategorycustom.forecast_categoryCUSTOM
OwnerIdowned_byNATIVE→ devu
OpportunityLineItem[]child custom objectCUSTOMlinks Product2/PricebookEntry
SystemModstampaudit fieldsNATIVE

Product2 SObject: Product2 CUSTOM

CRM Product = sellable catalog item (SKU, price, on quotes/orders). Not a DevRev Part — Part is the internal product/feature tree for issue routing. Model as a product-catalog custom object.

API columnDevRev targetTagNotes
Idexternal_uidCUSTOMproduct-catalog custom object
Namecustom.nameCUSTOM
ProductCodecustom.skuCUSTOMSKU = field on catalog object
Descriptioncustom.descriptionCUSTOM
Familycustom.familyCUSTOMcategory/grouping
IsActivecustom.is_activeCUSTOM
SystemModstampaudit fieldsCUSTOMdelta cursor

Pricebook2 / PricebookEntry SObject: Pricebook2 · PricebookEntry CUSTOM

API columnDevRev targetTagNotes
Pricebook2.Idexternal_uid (custom obj)CUSTOMreference data; load once
Pricebook2.Namecustom.nameCUSTOM
Pricebook2.IsActivecustom.is_activeCUSTOM
PricebookEntry.Product2Idcustom.product (ref → product-catalog obj)CUSTOM
PricebookEntry.UnitPricecustom.unit_price (int)CUSTOMminor units
PricebookEntry.CurrencyIsoCodecustom.currencyCUSTOM

User SObject: User

API columnDevRev targetTagNotes
Idexternal_refNATIVEowner-resolution join key
NameDev User display_nameNATIVE
EmailDev User emailNATIVE
ProfileId/UserRoleIdcustom.roleCUSTOM
IsActivestateNATIVEkeep deactivated (historical refs)

Quote SObject: Quote · QuoteLineItem CUSTOM

API columnDevRev targetTagNotes
Quote.Idexternal_uidCUSTOMheader custom object
Quote.Namecustom.nameCUSTOM
Quote.OpportunityIdcustom.opportunity (ref)CUSTOM
Quote.StatusstageCUSTOMstage diagram
Quote.TotalPrice / GrandTotalcustom.total (int)CUSTOMminor units
Quote.ExpirationDatecustom.expiration_dateCUSTOMoptionalTs
QuoteLineItem.*child custom objectCUSTOMProduct2Id, Quantity, UnitPrice

Order SObject: Order · OrderItem CUSTOM

API columnDevRev targetTagNotes
Order.Idexternal_uidCUSTOMheader custom object
Order.OrderNumbercustom.order_numberCUSTOM
Order.AccountIdcustom.account (ref)CUSTOM
Order.StatusstageCUSTOM
Order.TotalAmountcustom.total (int)CUSTOMminor units
Order.EffectiveDate / EndDatecustom.start_date / _end_dateCUSTOMoptionalTs
OrderItem.*child custom objectCUSTOMProduct2Id, Quantity, UnitPrice

Contract SObject: Contract CUSTOM

API columnDevRev targetTagNotes
Idexternal_uidCUSTOM
ContractNumbercustom.contract_numberCUSTOM
AccountIdcustom.account (ref)CUSTOM
StatusstageCUSTOM
StartDate / EndDatecustom.start_date / _end_dateCUSTOMoptionalTs
ContractTermcustom.term_months (int)CUSTOM

Campaign SObject: Campaign · CampaignMember CUSTOM

API columnDevRev targetTagNotes
Campaign.Idexternal_uidCUSTOM
Campaign.Namecustom.nameCUSTOM
Campaign.Type / Statuscustom.type / custom.statusCUSTOM
Campaign.StartDate / EndDatecustom.start_date / _end_dateCUSTOMoptionalTs
CampaignMember.ContactId / LeadIdjoin custom object → revuCUSTOMmember = Contact↔Campaign join
CampaignMember.Statuscustom.member_statusCUSTOM
Zoho CRM Modules API v2 · Fields API

Field names = Zoho api_name. id → external_ref/external_uid; Modified_Time → delta cursor.

Accounts Module: Accounts

api_nameDevRev targetTagNotes
idexternal_ref (+Rev Org)NATIVE
Account_Namedisplay_nameNATIVE
Websitedomain / websiteNATIVE
Industrycustom.industryCUSTOM
Annual_Revenuecustom.annual_revenueCUSTOM
Employeescustom.employee_countCUSTOM
Phonecustom.phoneCUSTOM
Billing_Street / Billing_City / Billing_State / Billing_Code / Billing_Countrycustom.billing_street / _city / _state / _zip / _countryCUSTOM+ Shipping_* → custom.shipping_*
Account_Typetier⚑ STD-NEEDEDif tier standard not enabled
Parent_Accountcustom.parent_accountCUSTOM
Ownerowned_byNATIVE→ devu
Modified_Timeaudit fieldsNATIVEdelta cursor

Contacts Module: Contacts

api_nameDevRev targetTagNotes
idexternal_refNATIVE
First_Name/Last_Namedisplay_name / full_nameNATIVE
EmailemailNATIVEmerge key
Phone/Mobilephone_numbers[]NATIVE
Titlecustom.job_titleCUSTOM
Account_Name (lookup)rev_org / accountNATIVEresolve
Lead_Sourcecustom.lead_sourceCUSTOM
Email_Opt_Outcustom.email_opt_outCUSTOM
Ownerowned_byNATIVE
Modified_Timeaudit fieldsNATIVE

Leads Module: Leads

api_nameDevRev targetTagNotes
idexternal_uid⚑ STD-NEEDEDnative Lead gated; else custom object
Full_Namedisplay_nameNATIVE
Companycustom.companyCUSTOM
Email/Phonecustom.email / custom.phoneCUSTOM
Lead_StatusstageNATIVEstage diagram
Lead_Sourcecustom.lead_sourceCUSTOM
Ratingcustom.scoreCUSTOM
Ownerowned_byNATIVE
Modified_Timeaudit fieldsNATIVE

Deals Module: Deals

api_nameDevRev targetTagNotes
idexternal_uid⚑ STD-NEEDEDnative Opportunity gated; else custom object
Deal_Namedisplay_nameNATIVE
Account_Namecustom.account (ref)NATIVEresolve
Amountcustom.amountCUSTOMminor units
Currencycustom.currencyCUSTOM
StagestageNATIVE1:1 mapping
Probabilitycustom.probabilityCUSTOM
Closing_Datecustom.close_dateCUSTOMoptionalTs
Pipelinecustom.pipelineCUSTOM
Ownerowned_byNATIVE
Modified_Timeaudit fieldsNATIVE

Products Module: Products CUSTOM

Sellable catalog item → product-catalog custom object (not DevRev Part).

api_nameDevRev targetTagNotes
idexternal_uidCUSTOMproduct-catalog custom object
Product_Namecustom.nameCUSTOM
Product_Codecustom.skuCUSTOM
Product_Categorycustom.familyCUSTOMcategory/grouping
Product_Activecustom.is_activeCUSTOM
Unit_Pricecustom.unit_priceCUSTOMminor units

Users Module: Users

api_nameDevRev targetTagNotes
idexternal_refNATIVEowner join key
full_nameDev User display_nameNATIVE
emailDev User emailNATIVEjoin key
role / profilecustom.roleCUSTOM
statusstateNATIVEkeep deactivated

Sales extras Price_Books · Quotes · Sales_Orders · Subscriptions · Campaigns CUSTOM

Module · key api_name fieldsDevRev targetTagNotes
Price_Books: Price_Book_Name, Pricing_Detailscustom objectCUSTOMreference data; load once
Quotes: Subject, Deal_Name, Grand_Total, Valid_Till, Quote_Stagecustom object (+child Quoted_Items)CUSTOMGrand_Total minor units; Valid_Till optionalTs
Sales_Orders: Subject, Account_Name, Grand_Total, Statuscustom object (+child Ordered_Items)CUSTOM
Subscriptions: Name, Status, Start_Date, Billing_Frequencycustom objectCUSTOMstage diagram for status
Campaigns: Campaign_Name, Type, Status, Start_Date, End_Datecustom objectCUSTOMmember link = join object
HubSpot CRM v3 · Properties API

Field names = HubSpot internal property names. hs_object_id → external_ref/external_uid (string); hs_lastmodifieddate → delta cursor.

Companies Object: company · type 0-2

propertyDevRev targetTagNotes
hs_object_idexternal_ref (+Rev Org)NATIVE
namedisplay_nameNATIVE
domaindomainNATIVEHubSpot unique key
industrycustom.industryCUSTOM
annualrevenuecustom.annual_revenueCUSTOM
numberofemployeescustom.employee_countCUSTOM
phonecustom.phoneCUSTOM
address / city / state / zip / countrycustom.address_street / _city / _state / _zip / _countryCUSTOMflatten to 5 fields
hubspot_owner_idowned_byNATIVE→ devu (via Owners API)
hs_lastmodifieddateaudit fieldsNATIVEdelta cursor

Contacts Object: contact · type 0-1

propertyDevRev targetTagNotes
hs_object_idexternal_refNATIVE
firstname/lastnamedisplay_name / full_nameNATIVE
emailemailNATIVEunique key
phone/mobilephonephone_numbers[]NATIVE
jobtitlecustom.job_titleCUSTOM
associated companyrev_org / accountNATIVEvia associations API
lifecyclestagecustom.lifecycle_stageCUSTOMlead/MQL/SQL/customer
hs_lead_statusstage (if lead)NATIVElifecycle=lead → Lead flow
hs_analytics_sourcecustom.lead_sourceCUSTOM
hubspot_owner_idowned_byNATIVE
hs_lastmodifieddateaudit fieldsNATIVE

HubSpot has no separate Lead object — a lead is a Contact with lifecyclestage=lead. Route to Lead flow only if the connector splits on lifecycle.

Deals Object: deal · type 0-3

propertyDevRev targetTagNotes
hs_object_idexternal_uid⚑ STD-NEEDEDnative Opportunity gated; else custom object
dealnamedisplay_nameNATIVE
associated companycustom.account (ref)NATIVEvia associations
amountcustom.amountCUSTOMminor units
deal_currency_codecustom.currencyCUSTOM
dealstagestageNATIVE1:1; internal stage IDs
pipelinecustom.pipelineCUSTOMmulti-pipeline
hs_deal_stage_probabilitycustom.probabilityCUSTOM
closedatecustom.close_dateCUSTOMoptionalTs
hubspot_owner_idowned_byNATIVE
line items (0-8)child custom objectCUSTOMvia associations
hs_lastmodifieddateaudit fieldsNATIVE

Products Object: product · type 0-7 CUSTOM

Sellable catalog item → product-catalog custom object (not DevRev Part).

propertyDevRev targetTagNotes
hs_object_idexternal_uidCUSTOMproduct-catalog custom object
namecustom.nameCUSTOM
hs_skucustom.skuCUSTOM
descriptioncustom.descriptionCUSTOM
pricecustom.unit_priceCUSTOMminor units
hs_lastmodifieddateaudit fieldsCUSTOMdelta cursor

Owner Owners API

fieldDevRev targetTagNotes
idexternal_refNATIVE= hubspot_owner_id refs
firstName / lastNameDev User display_nameNATIVE
emailDev User emailNATIVEjoin key
archivedstateNATIVEkeep archived (historical refs)

Line item & Quote line item 0-8 · quote 0-14 CUSTOM

propertyDevRev targetTagNotes
line_item.namecustom.nameCUSTOMchild of Deal
line_item.hs_product_idcustom.product (ref → product-catalog obj)CUSTOM
line_item.quantity / pricecustom.quantity / custom.unit_priceCUSTOMminor units
quote.hs_titlecustom.nameCUSTOMquote header custom object
quote.hs_statusstageCUSTOM
quote.hs_expiration_datecustom.expiration_dateCUSTOMoptionalTs
LeadSquared Lead/Opportunity/Activity/Task APIs · Get-Metadata

Schema field names via Get-Metadata. ProspectID / OpportunityId → external ids. Created-date biased — use hybrid delta (short modified window + periodic full sweep).

Lead Lead Management API · Lead = person

schema fieldDevRev targetTagNotes
ProspectIDexternal_ref (revu)NATIVELS Lead = person → Rev User
FirstName/LastNamedisplay_name / full_nameNATIVE
EmailAddressemailNATIVEmerge key
Phone/Mobilephone_numbers[]NATIVE
Companyaccount / custom.company⚑ STD-NEEDEDLS has no first-class Account for B2C; create Account+Rev Org if B2B
ProspectStagestageNATIVEstage diagram
Sourcecustom.lead_sourceCUSTOM
Score/EngagementScorecustom.scoreCUSTOM
OwnerId/Ownerowned_byNATIVE→ devu
ModifiedOnaudit fieldsNATIVEdelta (created-biased → hybrid)
CreatedOnaudit fieldsNATIVEprimary window field

Opportunity Opportunity Management API

schema fieldDevRev targetTagNotes
OpportunityIdexternal_uid⚑ STD-NEEDEDnative Opportunity gated; else custom object
OpportunityNamedisplay_nameNATIVE
ProspectID (owner lead)custom.contact (ref)NATIVElink to revu
Amount/Valuecustom.amountCUSTOMminor units
StagestageNATIVE1:1
Statuscustom.statusCUSTOM
ExpectedClosingDatecustom.close_dateCUSTOMoptionalTs
OwnerIdowned_byNATIVE
ModifiedOnaudit fieldsNATIVE

Activity Activity / Sales Activity Management API

schema fieldDevRev targetTagNotes
ActivityId / ProspectActivityIdTimeline entry id (external_ref)NATIVE
RelatedProspectIdparent revu (link)NATIVEentry attaches to Rev User
ActivityEvent / ActivityTypecustom.activity_typeCUSTOMenum; pre-seed activity-type picklist
ActivityNote / Notestimeline comment bodyNATIVE
Statuscustom.statusCUSTOM
Owner / CreatedBycreated_by (devu)NATIVE→ devu
CreatedOntimeline entry timestampNATIVE
ModifiedOnaudit fieldsNATIVEdelta cursor

Task Task Management API

schema fieldDevRev targetTagNotes
TaskId / Idexternal_refNATIVE
Name / SubjecttitleNATIVE
Description / NotesbodyNATIVE
StatusstageNATIVEopen/in_progress/completed
PrioritypriorityNATIVEmap High/Med/Low → P0–P3
DueDate / ReminderTimecustom.due_date (ts)CUSTOMoptionalTs
RelatedProspectId / RelatedEntityIdlinked objectNATIVEresolve target (revu/opportunity)
Category / TaskTypecustom.activity_typeCUSTOM
UserId / OwnerIdowned_by (devu)NATIVE→ devu
ModifiedOnaudit fieldsNATIVEdelta cursor

User User Management API

schema fieldDevRev targetTagNotes
UserIdexternal_refNATIVEowner-resolution join key
FirstName / LastNameDev User display_nameNATIVE
EmailAddressDev User emailNATIVEjoin key
Rolecustom.roleCUSTOM
IsActive / StatusstateNATIVEkeep deactivated (historical refs)

Standard activity types per tool

Every CRM logs interactions as "activities". Coverage differs. This is what feeds the polymorphic Task / Calls / email recommendations below.

ActivitySalesforceZoho CRMHubSpotLeadSquaredDevRev target
Task / To-doTask (polymorphic WhoId/WhatId)Taskstask engagement (0-27)Task entity (Task/Create)Task (work) — polymorphic ⚑
Call / PhoneTask w/ TaskSubtype=Call, CallDurationInSeconds, CallDispositionCalls module (Call_Type, Call_Duration, Call_Result)call engagement (recording, disposition, duration)phone-call Activity (ActivityEvent code)Calls object ⚑
Meeting / EventEvent (calendar, polymorphic)Meetings/Events (Activities sub-module)meeting engagement (attendees, outcome)meeting ActivityTimeline entry / Calls-style object
EmailEmailMessage + EmailMessageRelation (to/cc/bcc); Task subtype=EmailEmails (Sent/Received, per record)email engagement (open/click events)email Activity + Email campaignsTimeline entry (email) + Email composer ⚑
NoteContentNote / NoteNotes modulenote engagementNoteTimeline comment
Custom activityTask/Event subtypes + custom fieldscustom Activitiescustom engagement propscustom Activity types (numeric ActivityEvent; heavy usage — enumerate first)custom.activity_type enum / Timeline
System / auto activityfield-history, FeedItemBlueprint transitionslifecycle-stage change, page-visit, form-submitstatus-change, page-visit, email-opened (system ActivityEvents)Timeline entries (system)
Model differences. SFDC folds Call & Email into Task subtypes (+ Event for meetings). Zoho splits into distinct modules (Calls / Meetings / Emails / Tasks / Notes). HubSpot = 5 unified engagement objects on one timeline. LeadSquared models nearly everything as an Activity keyed by a numeric ActivityEventTask is the one distinct entity; custom activity types are pervasive and must be enumerated via metadata before extraction or history is lost. ⚑ STD-NEEDED = drives the Calls-object & polymorphic-Task recommendations.

Outbound email editor ⚑ STD-NEEDED

Salesforce, Zoho, HubSpot, LeadSquared all let a rep compose & send email from the record (Lead/Contact/Deal) with templates, merge fields, and attachments. DevRev has a rich email/compose editor on the support/conversation side — the CRM/sales surface has no equivalent. A clean replacement needs a sales-record email composer. Flag as product gap; interim = reuse the support conversation editor scoped to the record, or a snap-in.

Capabilities the CRM composer must cover

CapabilitySource CRM equivalentDevRev todayTag
Compose from Lead/Contact/Deal recordSFDC Activity email · HubSpot "Email" tab · Zoho Send Mail · LS Send Emailonly support conversations / PLuG⚑ STD-NEEDED
Rich HTML body + plain-text fallbackall four (WYSIWYG + source)rich text in timeline/conversation only⚑ STD-NEEDED
Templates (save / pick / share)SFDC Email Templates · HubSpot Templates · Zoho Email Templates · LS Templatesno sales-email template store⚑ STD-NEEDED
Merge / personalization tokens{{Contact.FirstName}}, deal fields, owner signone on sales records⚑ STD-NEEDED
Attachmentsfiles + content-library docsArtifacts exist (attach + vectorize)NATIVE*
Logged/threaded on the record timelineemail logged to Activity timelineTimeline entries nativeNATIVE
Send + delivery/open trackingall four track sends/opensno sales-side send infra⚑ STD-NEEDED

* Attachments: the Artifact primitive exists — wire the composer to attach Artifacts. Body storage can reuse rich_text (HTML) + a derived plain-text field; EDM/IDM must agree on the type (rich_text split-brain rejects on recipe discovery).

Migrating existing templates & sent email

Source objectDevRev targetTagNotes
Email template (EmailTemplate / HubSpot template / Zoho / LS)native Template object (subtype=email)⚑ STD-NEEDEDone store for all channels — see Template object
Sent email (EmailMessage / engagement / activity)Timeline entry (email type)NATIVEfrom/to/subject/body/sent_at; attachments → Artifacts
Email attachmentsArtifactsNATIVElink to the timeline entry

Telephony & messaging channels ⚑ STD-NEEDED

CRMs drive outreach over voice, SMS, and WhatsApp — not just email. To replace one, DevRev needs generic, provider-agnostic connectors for each channel that support multiple profiles (many numbers/senders/accounts per org) and template sync (pull approved templates from the provider, incl. India DLT & WhatsApp HSM). All channel events land on the record timeline; recordings/media → Artifacts.

Channels & the connectors to build

ChannelWhat it must doProviders to abstractTag
Telephony / CTI (voice)click-to-call, inbound routing, call recording→Artifact, transcript, disposition, duration, direction; screen-pop the matched recordTwilio · Exotel · Ozonetel · Knowlarity (Gupshup) · Servetel/Tata Tele · Airtel IQ · MyOperator · Plivo · Amazon Connect · Genesys · generic SIP⚑ STD-NEEDED
SMSsend/receive, DLR (delivery receipts), sender-ID profiles, template + DLT sync (India), opt-out/STOP handlingTwilio · MSG91 · Gupshup · Netcore · Kaleyra (Tata) · Sinch · Karix · Infobip · Plivo · Vonage · Airtel IQ⚑ STD-NEEDED
WhatsApp (Business)WABA profiles, HSM/approved-template sync, session vs template messages, media, 24h window rules, opt-inMeta Cloud API (direct) · Twilio · Gupshup · Netcore · WATI · Interakt · AiSensy · Gallabox · Zoko · Haptik · Yellow.ai · DoubleTick · 360dialog · Infobip · Kaleyra · MSG91 · Karix · Wappnet · Charles · Verloop⚑ STD-NEEDED
RCS / other IM (future)same shape as SMS/WhatsAppprovider-dependent⚑ STD-NEEDED

Generic-connector requirements (apply to all channels)

RequirementDetail
Provider-agnostic interfaceone channel abstraction, provider = a pluggable adapter (like AirSync's connector shape). Swap Twilio↔MSG91 without changing the record surface.
Multiple profilesan org runs many senders — several phone numbers / short codes / sender IDs / WABA numbers, each with its own keyring. Route by brand/team/campaign. Profile = credential set + sender identity.
Template syncpull provider-side approved templates on a schedule: WhatsApp HSM (name, language, category, variables, approval status), SMS DLT (India — template ID, entity ID, header/sender), email templates. Read-only mirror + local overrides.
Merge / variablestemplate placeholders bound to record fields ({{1}},{{2}} for WhatsApp; named tokens for SMS/email).
Inbound + outbound on timelineevery message/call = a typed Activity/Timeline entry on the matched Contact/Lead/Opp; media & recordings → Artifacts.
Delivery/read receipts & eventssent/delivered/read/failed, call disposition, DLR — captured as status on the entry.
Consent & complianceopt-in/opt-out per channel, STOP keyword, WhatsApp 24h session window, DLT registration, TCPA/DND — enforce before send.
Keyring / secretsprovider creds in developer_keyrings per profile — never in manifest; OAuth or API-key per provider.

Vendor landscape (build adapters for the high-volume ones first)

Popular CPaaS / telephony / WhatsApp-BSP vendors. ✓ = strong offering. Prioritize by customer footprint — India POCs skew MSG91 / Gupshup / Exotel / Netcore; global skews Twilio / Infobip / Sinch.

VendorVoice/CTISMSWhatsAppRegion strengthNotes
TwilioGlobalbroadest API; Programmable Voice/Messaging; WhatsApp via Twilio
MSG91IndiaDLT-native SMS; OTP; WhatsApp; Hello voice
Gupshup✓ (Knowlarity)India / EMmajor WhatsApp BSP; acquired Knowlarity (voice)
NetcoreIndia / SEACPaaS + CE; email (Pepipost); WhatsApp + RCS
ExotelIndia / SEAcloud telephony leader; IVR, call center, number masking
OzonetelIndia / USCloudAgent contact center; CTI connectors
Kaleyra (Tata Comms)India / Globalenterprise CPaaS; now part of Tata Communications
InfobipGlobal / EMEAomnichannel CPaaS; strong WhatsApp/Viber/RCS
SinchGlobalCPaaS; SMS/voice/WhatsApp/RCS
PlivoGlobal / Indiavoice + messaging APIs; competitive pricing
Vonage (Ericsson)GlobalNexmo APIs; voice/SMS/WhatsApp
Airtel IQIndiatelco-native CPaaS; carrier-grade delivery
Tata Tele / ServetelIndiacloud telephony, toll-free, IVR
KnowlarityIndia / SEASuperReceptionist; now under Gupshup
MyOperatorIndiaSMB cloud call management
WATI · Interakt · AiSensy · Gallabox · Zoko · DoubleTick · WappnetIndia / SMBWhatsApp-first BSPs; shared-inbox, template + broadcast/campaign tooling, chatbot builders
360dialog · Charles · Zoko · RasayelGlobal / EUWhatsApp BSPs; 360dialog = direct Cloud-API reseller (no markup on conversations)
Haptik · Yellow.ai · Verloop · Gupshup BotIndia / Globalconversational-AI BSPs — WhatsApp + bot/agent-assist layer on top
Amazon Connect · Genesys · Five9 · NICEGlobal (enterprise CC)full contact-center platforms; heavier CTI integration
Bird (MessageBird)Global / EMEAomnichannel CPaaS; WhatsApp BSP
TelnyxGlobal / US-EUcarrier-grade voice/SMS; private network
BandwidthUS / Globalowns network; enterprise voice/messaging + 911
RingCentral · 8x8 · DialpadUS / GlobalUCaaS/CCaaS; embedded CTI, native CRM dialers
Telesign · Bandyer/Kaleyra VideoGlobalverification/OTP-heavy SMS
Zenvia · Take BlipLATAM (Brazil)regional WhatsApp/SMS leaders
Africa's Talking · ClickatellAfricaClickatell = early WhatsApp BSP; strong Africa/EM
Unifonic · CequensMENAMiddle East regional CPaaS
LINE · KakaoTalk · Viber · Zalo · WeChat✓*APAC / regional IM*channel-equivalents to WhatsApp — JP/KR/VN/CN/SEA; abstract as messaging channels
Regional messaging beyond WhatsApp. In several markets the dominant channel isn't WhatsApp: LINE (Japan/Thailand/Taiwan), KakaoTalk (Korea), Zalo (Vietnam), WeChat (China), Viber (EE/EMEA). A truly generic messaging connector treats these as additional channel adapters — same profile + template-sync shape as WhatsApp.
BSP is an adapter, not a hard dependency. All WhatsApp BSPs sit on Meta's WhatsApp Cloud API — the connector should abstract "WhatsApp channel" once and treat each BSP (Gupshup, WATI, Gallabox, Haptik, 360dialog, AiSensy, Interakt, Zoko …) as a swappable adapter/profile. 360dialog resells the Cloud API directly (no per-conversation markup); AI BSPs (Haptik, Yellow.ai, Verloop) add a bot/agent layer. Customer picks the BSP; DevRev shouldn't hard-wire one. Same pattern as AirSync connectors.

Native Template object ⚑ STD-NEEDED — one store, vendor-differentiated fields

Don't scatter templates across per-connector customs. One standard Template object with a common core + a channel-typed field set (differentiated by channel/provider). subtype = channel (email / sms / whatsapp / voice-script) selects which vendor-specific fields apply. Filter/query by channel · provider · profile · approval_status.

FieldScopeTypeNote
subtype (channel)coreenumemail · sms · whatsapp · voice — selects vendor field set
namecoretext
providercoreenumTwilio · MSG91 · Gupshup · Meta … — which BSP/CPaaS owns it
profilecorerefwhich sender/WABA/number profile
bodycorerich_texttext/HTML per channel
variables[]corelistmerge tokens ({{1}}, named)
languagecoreenum
approval_statuscoreenumdraft · submitted · approved · rejected · paused — synced from provider
external_template_idcoreidprovider's template id (sync key)
subjectemailtextemail only
from / reply_toemailtextemail only
hsm_categorywhatsappenumMARKETING / UTILITY / AUTHENTICATION
header / footer / buttonswhatsappstructHSM components; media header
quality_ratingwhatsappenumgreen/yellow/red — from Meta
dlt_template_id / entity_id / sender_idsmsidIndia DLT registration
us_campaign_id (10DLC)smsidUS brand/campaign reg
script_steps / ivr_flowvoicestructcall script / IVR
Sync model. Template store is a two-way mirror: provider is source-of-truth for approval_status/quality_rating (pull on schedule); DevRev owns local drafts + which profile/brand uses which template. Block send on non-approved/paused. Same shape covers email templates too — unifies the email composer template need.

Migrating existing channel data

SourceDevRev targetTag
SMS/WhatsApp templates (DLT/HSM), call scriptsnative Template object (subtype=channel) — see above⚑ STD-NEEDED
Call logs (SFDC Task/Call, HS call, Zoho Calls, LS activity)Calls object / Activity (see recos)
SMS / WhatsApp message historyTimeline entries (typed)NATIVE
Recordings / mediaArtifactsNATIVE
Regional sender-registration regimes — sync approval status, block on unapproved. India: SMS DLT template + entity registration (or telcos drop it). US: 10DLC brand/campaign registration + toll-free verification (unregistered traffic filtered). UK/EU: sender-ID pre-registration in some networks; GDPR consent. WhatsApp (all regions): HSM template approval + WABA display-name review + quality-rating tiers. The connector must sync per-region approval state and block sends on unapproved/paused templates.

DevRev product recommendations (what to build for a clean CRM)

To be a true CRM replacement — not a data mirror — DevRev needs these as standard objects/behaviours. Prioritized.

P0 · Lead as a first-class standard object

RequirementDetail
Separate object (not a Rev User flag)Unqualified prospect w/ its own lifecycle, before Account/Contact exist. All 4 CRMs have it.
Stage lifecycleNew → Working → Qualified → Converted/Disqualified. Stage diagram, 1:1 with source Lead_Status.
Convert actionLead → Account + Contact(revu) + Opportunity in one step; preserve source external_ref links.
Fieldsname, company (text pre-convert), email, phone, source, score/rating, owner (devu), stage.
Why standardCustom object loses convert flow, dedup-on-convert, and pipeline UI. Core CRM semantics.

P0 · Task as a polymorphic object (link to ANY object)

RequirementDetail
One Task object, many parentsA Task must attach to Contact, Lead, Account, Opportunity, Ticket, Issue — and ideally Conversation/Part.
Polymorphic related_toMirrors SFDC WhatId/WhoId. DevRev links aren't polymorphic today → this is the gap. Resolve target type at transform until native.
Multi-linke.g. Task linked to both a Contact (who) and an Opportunity (what) simultaneously.
Fieldstitle, body, stage (open/in_progress/completed), priority, due_date, owner (devu), related_to[].
Why standardSales activity threads across the whole graph; a per-object custom task fragments reporting & "my open tasks" views.
Cross-object linkage is the crux. The ask is one Task type whose parent can be any of Contact / Lead / Account / Opportunity / Ticket / Issue — not six separate task customs. Needs a polymorphic reference primitive on DevRev's side.

P1 · Calls as a separate object

RequirementDetail
Distinct from Task & timeline noteSFDC Task(TaskSubtype=Call) · Zoho Calls · HubSpot Call engagement · LS phone Activity. Own object.
Call-specific fieldsdirection (in/out), duration, call_result/disposition, recording (Artifact), phone_number, call_time.
Polymorphic parentSame as Task — call logs against Contact / Lead / Opportunity / Account.
Transcript / recordingrecording → Artifact; transcript → timeline entry or body (rich_text, vectorizable).
Why standardCall analytics (volume, disposition, connect rate) need a typed object, not free-text notes.

Supporting standards (already flagged)

Object / fieldRecommendation
Opportunity / DealFirst-class standard w/ amount, pipeline, stage, forecast, close_date — confirm plan entitlement or enable.
Money + currency typeNative money field (amount + ISO currency in one), not minor-units-in-int.
Account tier/segmentStandard field on Account for segmentation/routing.
Product-catalog objectSellable catalog (SKU, price) as first-class sales entity — distinct from Part.
Sales email composerRecord-level HTML+text email w/ templates, merge tokens, attachments, send/tracking.

★ DevRev Grow module — proposed standard objects & fields

Synthesis of Salesforce + Zoho + HubSpot + LeadSquared. Inclusion rule: a field/object becomes a Grow standard only if it's a stock (out-of-box) field in ≥3 of 4 CRMs (>75%). This is the minimum standard surface that lets Grow replace a CRM without every customer dropping to custom fields on day one. Coverage column = how many of the 4 tools ship it stock.

How to read. 4/4 = universal, must be standard. 3/4 = >75%, standard. Fields present in ≤2/4 stay custom (not listed — they're the long tail). "cvg" = coverage.
Subtype = record-type discriminator. Every Grow object carries a subtype (DevRev custom leaf-type / subtype) — the equivalent of SFDC Record Type, HubSpot record type, Zoho Layout. One object, many business variants (Account: Customer/Partner/Prospect · Lead: Inbound/Outbound · Opp: New-Business/Renewal/Upsell). Subtype drives which custom fields, stages, and layouts apply. Already supported in DevRev — standard, not a gap. Preserve source RecordTypeId/Layout → subtype or picklist validation breaks on load.

Object 1 · Account 4/4 — SFDC Account · Zoho Accounts · HS Company · LS Account

Grow standard fieldTypecvgSource stock fields
subtypesubtype4/4record-type: Customer/Partner/Prospect/Reseller — SFDC RecordType · Zoho Layout · HS record type · LS type
nametext4/4Name · Account_Name · name · Account
domain / websiteurl4/4Website · Website · domain · Website
phonephone4/4Phone (all)
industryenum4/4Industry (all)
annual_revenuemoney3/4AnnualRevenue · Annual_Revenue · annualrevenue
employee_countint3/4NumberOfEmployees · Employees · numberofemployees
billing_address (5 parts)address4/4Billing* · Billing_* · address/city… · Address
account_type / tierenum3/4Type · Account_Type · (HS custom) · —
ownerref(user)4/4OwnerId · Owner · hubspot_owner_id · OwnerId
parent_accountref(self)3/4ParentId · Parent_Account · (HS parent)
created_at / updated_atts4/4audit fields (all)

Object 2 · Contact 4/4 — SFDC Contact · Zoho Contacts · HS Contact · LS Lead(person)

Grow standard fieldTypecvgSource stock fields
subtypesubtype4/4record-type: Buyer/Influencer/Champion/Technical — SFDC RecordType · Zoho Layout · HS record type
first_name / last_nametext4/4all
emailemail4/4Email · Email · email · EmailAddress
phone / mobilephone[]4/4all
job_titletext4/4Title · Title · jobtitle · JobTitle
accountref(account)4/4AccountId · Account_Name · assoc company · Company
lead_sourceenum4/4LeadSource · Lead_Source · hs_analytics_source · Source
lifecycle_stageenum3/4(SFDC status) · Lead_Status · lifecyclestage · ProspectStage
ownerref(user)4/4all
email_opt_outbool4/4HasOptedOutOfEmail · Email_Opt_Out · hs_email_optout · DoNotEmail
mailing_addressaddress3/4Mailing* · Mailing_* · address*
created_at / updated_atts4/4all

Object 3 · Lead 3/4 stock separate object — SFDC · Zoho · LS · (HS = contact lifecycle) ⚑ new standard

Grow standard fieldTypecvgSource stock fields
subtypesubtype3/4record-type: Inbound/Outbound/Partner-referral — SFDC RecordType · Zoho Layout · LS type
nametext4/4all
companytext (not ref)3/4free-text — no Account exists pre-conversion; becomes ref via converted_account on convert
email / phoneemail/phone4/4all
status / stageenum(stage)4/4Status · Lead_Status · hs_lead_status · ProspectStage
sourceenum4/4LeadSource · Lead_Source · source · Source
rating / scoreint4/4Rating · Rating · hubspotscore · Score
ownerref(user)4/4all
converted (flag + refs)bool + refs3/4IsConverted+Converted* · Converted · (HS stage change) · Conversion

Object 4 · Opportunity / Deal 4/4 — SFDC Opportunity · Zoho Deals · HS Deal · LS Opportunity

Grow standard fieldTypecvgSource stock fields
subtypesubtype4/4record-type: New-Business/Renewal/Upsell/Cross-sell — SFDC RecordType · Zoho Layout · HS record type · LS type
nametext4/4Name · Deal_Name · dealname · OpportunityName
accountref(account)4/4AccountId · Account_Name · assoc · (via lead)
amountmoney4/4Amount · Amount · amount · Amount
currencyenum3/4CurrencyIsoCode · Currency · deal_currency_code · —
stageenum(stage)4/4StageName · Stage · dealstage · Stage
pipelineenum3/4(SFDC RecordType) · Pipeline · pipeline · —
probabilitypercent4/4Probability · Probability · hs_deal_stage_probability · Probability
close_datedate4/4CloseDate · Closing_Date · closedate · ExpectedClosingDate
ownerref(user)4/4all
forecast_categoryenum3/4ForecastCategory · (Zoho) · hs_forecast_category · —
line_itemschild obj4/4OpportunityLineItem · Product_Details · line items · —

Object 5 · Activity (Task / Call / Meeting / Email / Note) 4/4 — polymorphic ⚑ polymorphic ref needed

Grow standard fieldTypecvgNote
typeenum(task/call/meeting/email/note)4/4all log these 5 stock activity types
subtypesubtype3/4per-type variant: demo/discovery/follow-up call; SFDC Task/Event RecordType · Zoho · LS custom ActivityEvent
subject / titletext4/4all
body / descriptionrich_text4/4all
related_to[]polymorphic ref4/4Account/Contact/Lead/Opp/Ticket/Issue — WhoId/WhatId analog
owner / assigneeref(user)4/4all
statusenum4/4open/in_progress/completed
due_datets4/4task due / activity date
direction, duration, dispositionenum/int3/4call-specific (SFDC/Zoho/HS); LS via activity fields
attachmentsArtifact[]4/4recordings, files

Object 6 · Product-catalog item 3/4 — SFDC Product2 · Zoho Products · HS Product · (LS —) ⚑ distinct from Part

Grow standard fieldTypecvgSource
subtypesubtype3/4record-type: Good/Service/Subscription/Bundle
nametext3/4Name · Product_Name · name
sku / codetext3/4ProductCode · Product_Code · hs_sku
unit_pricemoney3/4(Pricebook) · Unit_Price · price
family / categoryenum3/4Family · Product_Category · (HS)
is_activebool3/4IsActive · Product_Active · (HS)
Grow standard surface = 6 objects. Account · Contact · Lead · Opportunity · Activity (polymorphic) · Product-catalog. Shipping these with the fields above covers >75% of stock CRM fields across all four tools — the remaining long tail (≤2/4 coverage) stays custom. Two platform primitives unlock it: (1) polymorphic reference (Activity.related_to, Task/Call linkage) and (2) native money type (amount+currency). Both already flagged in STD-NEEDED.

Commonly-forgotten objects (scope these or lose data)

A sales migration that only maps Account/Contact/Lead/Opportunity silently drops these. Decide migrate vs archive vs drop for each.

Salesforce

ObjectWhat it holdsMigration note
ContentNote · ContentDocument / ContentVersion / ContentDocumentLinkmodern Notes & Filesbinary via VersionData; re-link through ContentDocumentLink junction → Artifacts
Attachment (classic)legacy attachments→ Artifacts
OpportunityContactRole / ContactRoleM2M contact↔opp + role (Decision Maker…)first-class junction; role often dropped
AccountTeamMember · OpportunityTeamMember · OpportunitySplitshared ownership / credit splitlost if only Account/Opp migrated
EmailMessage + EmailMessageRelationlogged emails, to/cc/bcc→ Timeline entry (email); relations = recipients
Eventcalendar entries (polymorphic)→ Calls/meetings object or timeline
Territory2 · UserTerritory2Association · ObjectTerritory2Associationterritory assignmentdrives account routing; usually re-modeled
ForecastingQuota · ForecastingItemquota/forecastmay need special API enablement — verify accessibility
UserRole · *Share (AccountShare…)role hierarchy, sharingre-model access, don't 1:1 migrate; document implications

Zoho CRM

ModuleNote
Notesattached to any record via parent id
Attachmentsseparate binary download endpoint per record → Artifacts
Calls · Meetings/Events (Activities sub-modules)→ Calls object / timeline
Quotes / Sales_Orders / Invoices line items (subforms)nested line-item arrays inside parent JSON — extract nested, don't flatten away
Vendors · Price_Books · Purchase_Ordersoverlooked in sales migrations
Multi-select lookupM2M via auto-created linking module — extract link records (junction)
Custom modulesdiscover via settings/modules metadata

HubSpot

Object / conceptNote
Engagements: notes, emails, calls, meetings, tasksfirst-class objects; the activity timeline is reconstructed from these + associations — migrating deals w/o engagements loses interaction history
Call/email metadatarecordings, disposition, duration, open/click events, meeting attendees
Association labels + primary flagsemantic role (Decision maker, Billing contact) lives on the link; primary company keys reporting — lost if only raw links migrated
Deal & Ticket pipelinesordered stages + stage IDs differ per portal; migrate pipeline defs before records
Line items / products (0-8 / 0-7)separate objects via associations
Marketing (forms, lists, workflows, campaigns)Marketing API, outside CRM v3 core — often not in a "CRM" export
Custom objectsmigrate schema (schemas API) before records

LeadSquared

EntityNote
Activities incl. custom Activity typesmost interaction history = activities keyed by numeric ActivityEvent; enumerate custom activity schemas first or lose history
Email campaigns / email historyseparate
Lists & Segmentsstatic lists vs dynamic segments
Sales groups / user hierarchymanager→rep reporting & ownership
Notes · Tasks/RemindersTask is a distinct entity from Activity
Opportunity + custom fieldsopportunity mgmt is an add-on — may not be enabled

Field-type traps (extraction/transform gotchas)

TrapWhereHandling
Compound fields (Name, Address, Geolocation)SFDCnot queryable as one column in Bulk API — extract component subfields (BillingStreet…, __latitude__s); flatten on load
Formula / roll-up summarySFDC, Zoho, HubSpot calc propscomputed & read-only — cannot write on load; snapshot resolved value or recompute in DevRev
Multi-select picklistSFDC (A;B;C semicolon), Zohosplit delimiter; target must accept multiple enum/tag values
Enum internal value ≠ labelHubSpot, allmap on option internal value, not display label; pre-seed enum (schemas.custom.set strips unknown values)
Multi-currency + dated FXSFDC DatedConversionRateconverted amounts are runtime-only; extract raw amount + CurrencyIsoCode + close date, never converted value
Master-detail vs lookupSFDCmaster-detail = cascade delete + roll-ups + required; affects load order & orphan behavior
Junction / M2M objectsSFDC junctions, Zoho multi-select lookupmigrate as first-class link records, not flattened
Subforms (nested line items)Zoho quotes/orders/invoicesline items embedded in parent JSON — extract nested
Record TypesSFDCcontrol picklist subsets + layouts; preserve RecordTypeId or picklist validation fails
Datetime = UTC / date-only midnight-UTCHubSpotnormalize TZ; empty ts → optionalTs (loader rejects parsing time "")
Owner IDs are internalHubSpot owners, allremap to DevRev devu via email, not by raw ID

Source API limits (size the connector for these)

Read limits live at runtime. Vendors revise these often. SFDC → /services/data/vXX.0/limits. Zoho → org API-usage dashboard. HubSpot → X-HubSpot-RateLimit-* headers. Numbers below marked verify are best-known, not contractual.
CRMRate / dailyBulkNotes
Salesforcedaily API = 15,000 base + per-license (Ent +1k/lic, Unl +5k/lic); ~1M/day ceiling (verify); 25 concurrent long-running (Bulk exempt)Bulk 2.0: server auto-chunks, 150M records/day ingest; ~100M query/day (verify); PK-chunking 100k default/250k maxbulk doesn't burn REST calls; poll status w/ backoff
Zoho CRMcredit model per edition/licenses; ~250–2,000 credits/user/day, ~1M/day cap (verify); concurrency ~10–25Bulk Read ≤ 200k rec/job; Bulk Write ≤ 25k rec/job (zipped CSV), ~5 concurrent jobsstandard pages max 200/page, ≤2,000 total → use Bulk beyond
HubSpotburst 100–150/10s; daily 250k (Free/Starter) / 500k–650k (Pro/Ent) (verify); API add-on → ~1M/daybatch read/create ≤ 100/req; assoc batch read 1,000 / create 2,000 (verified live)Search API tighter: 4 req/s, max 10,000 total results, index lag on new records
LeadSquared~300–500 req/min per key (verify); 429 ExceededApiLimitBulk Lead API async, job-basedregion-specific host (api-in21 / api-us11…) — wrong host = failure; accessKey+secretKey query auth

Migration strategy (half the job is non-data)

Object-field mapping is necessary, not sufficient. A CRM replacement also needs cutover, dedup, delete/merge detection, automation & reporting rebuild, reconciliation.

Cutover

Data quality, deletes & merges

One-way vs bi-directional

Parity & reconciliation (not migrated — rebuilt)

Compliance

Brands · Teams · Groups · Spaces (multi-brand + org structure)

DevRev's multi-brand + membership model. This is the target for CRM org-structure/sharing concepts earlier marked "re-model, don't 1:1 migrate" — divisions, business units, sales teams, territories, public groups.

Two concepts, kept separate. Brands = the multi-brand construct (a customer-facing brand). Teams handle those brands. Membership is not set on the brand directly — it's managed via Groups, which are then assigned to brands and teams. Spaces scope the work surface.

Brands API (as provided)

EndpointPurpose
/brands.createcreate a new brand
/brands.get (GET & POST)retrieve a brand by ID
/brands.list (GET & POST)list brands; cursor-based pagination + filters
/brands.updateupdate an existing brand
/brands.deletedelete a brand

Membership flow: create Group → assign devusers to Group → assign Group to Brand and/or Team. Don't attach users to the brand directly.

CRM org-structure → DevRev mapping

Source conceptToolsDevRev targetNote
Multi-brand / division / business unitSFDC Division · HubSpot Business Unit · Zoho (portals) · LS: noneBrandvia brands.create; one Brand per source BU/division. LeadSquared has no multi-brand construct — separate LS accounts per brand
Sales team / support teamSFDC Public Group / Team · HubSpot Team · Zoho Groups · LS Teams (user-grouping object, not multi-brand)Team + GroupTeam handles brand; Group holds members. LS Teams = distribution/ownership grouping only
Sales group / user hierarchyLeadSquared Sales Groups & Teams · SFDC Role HierarchyGroup (membership)manager→rep structure → group membership; hierarchy re-modeled, not 1:1
TerritorySFDC Territory2Group / Team assignmentaccount-routing intent → group→brand/team assignment
Sharing rules / *ShareSFDC AccountShare, OpportunityShareGroup-based visibilityaccess re-modeled via groups; document, don't 1:1 migrate
Record ownerallDev User (via Group)owner resolution unchanged; group drives visibility
Load order. Brands & Groups & Teams before records that reference them (owner visibility). Sequence: Brands → Teams → Groups → devuser→Group assignment → Group→Brand/Team assignment → then Accounts/Contacts/Opps.
Doc note. Only the Brands API was provided (message truncated). Teams / Groups / Spaces endpoints + field specs not yet captured — paste them and this section extends. Verify Brands endpoints against the live API spec before building.

AirSync mechanics (all tools)

⚑ STD-NEEDED gaps to raise with product

Where a clean CRM replacement needs a DevRev standard that isn't available today. Don't bury these in custom — flag them.

GapAffected toolsWhy standard, not custom
Standard Lead object (leaf type + stage lifecycle + convert action)SFDC, Zoho, HubSpot(lifecycle), LeadSquaredLead→Account/Contact/Opp conversion is core CRM semantics; custom loses convert flow & pipeline UI
Standard Opportunity/Deal object (amount, pipeline, forecast, close date)All fourPipeline is the CRM's spine; org/plan-gated today — confirm entitlement or file for enablement
Account tier/segment standard fieldSFDC Type, Zoho Account_TypeSegmentation drives routing/reporting; belongs on Account standard, not per-connector custom
First-class Account for B2C-oriented CRMsLeadSquaredLS keys on person; B2B rollups need a real Account, not a text field
Standard money + currency field type (amount+ISO in one)All fourEvery deal has money; minor-units-in-custom-int is fragile & loses currency semantics
Sales-record email composer (HTML+text, templates, merge tokens, attachments, send/tracking)All fourReps send email from Lead/Contact/Deal; DevRev email editor is support-side only — see Email-editor section
Product-catalog object (SKU, price, catalog) as a first-class sales entityAll fourCRM Product ≠ DevRev Part (Part = internal feature tree); modeled as custom object today, no sales-catalog standard
Polymorphic Task linkable to Contact/Lead/Account/Opp/Ticket/IssueAll fourDevRev links aren't polymorphic; six per-object task customs fragment reporting — see Recommendations P0
Calls as a separate object (direction, duration, disposition, recording)SFDC, Zoho, HubSpot, LSCall analytics need a typed object, not free-text timeline notes — see Recommendations P1
Generic telephony / SMS / WhatsApp connectors (multi-profile, template + DLT/HSM sync)All four (outreach channels)CRMs drive voice/SMS/WhatsApp outreach; DevRev needs provider-agnostic channel connectors — see Telephony & messaging
Native Template object (one store, channel/vendor-differentiated fields, provider approval sync)All four (email/SMS/WhatsApp/voice)templates shouldn't scatter across per-connector customs; one standard object w/ subtype=channel + HSM/DLT/10DLC fields — see Template object

★ Suggested schema for a proper Grow module DevRev-arch · AirSync-compliant

Each object below is a DevRev custom leaf type defined via schemas.custom.set (id_prefix + leaf_type_display_name + is_custom_leaf_type) and populated over AirSync (EDM → IDM). The relational DDL is a readable sketch — on-platform each column becomes a custom_schema_fragment field of a DevRev-supported type, and each row is a leaf-type record keyed by external_ref (native mapping) or external_uid (custom leaf).

AirSync compliance rules baked in. (1) external_ref on native-mapped objects, external_uid on custom leaf types (fresh_custom recipe). (2) EDM and IDM must agree on every field type — rich_text split-brain rejects at recipe discovery; fix both halves. (3) Enums pre-seeded before first load (schemas.custom.set silently strips unknown values). (4) Nullable timestamps use optionalTs (empty string → loader parsing time ""). (5) Stages via stage-diagram, deprecate to drop (reachability); enums force-alphabetized → zero-pad prefixes. (6) Secrets in developer_keyrings, referenced by id.

SQL sketch → DevRev field type (what the platform supports today)

Sketch typeDevRev field typeAirSync note
text / citexttextemail as text (indexed via is_indexed)
text[] (phone_numbers)tokens (array)multi-value
int / numericint / double
boolbool
timestamptz / datetimestampoptionalTs on nullable
enum / status / stageenum (+ stage diagram for stage)pre-seed values
rich bodyrich_textEDM/IDM must match
FK → objectid reference (typed to leaf)resolve at transform; load parents first
jsonb (address, components)flatten to fields or structprefer flat fields for filtering
money (amount+currency)no native typeint minor-units + enum currency⚑ STD-NEEDED native money
polymorphic_refnot supported → join leaf type (activity_link)⚑ STD-NEEDED polymorphic reference
Two gaps the DDL works around, not solves. DevRev today has no native money type (model as int minor-units + currency enum) and no polymorphic reference (model activity→any-object as a join leaf type activity_link). Both are in STD-NEEDED — the schema is AirSync-loadable as-is, but these primitives would make it first-class.

Shared types & enums

-- reusable composite + enums
CREATE TYPE money AS (amount_minor bigint, currency char(3));
CREATE TYPE object_kind AS ENUM (
  'account','contact','lead','opportunity','product','activity',
  'ticket','issue','conversation','part');   -- polymorphic targets
CREATE TYPE channel AS ENUM ('email','sms','whatsapp','voice');
CREATE TYPE approval_status AS ENUM ('draft','submitted','approved','rejected','paused');

dev_user · group · team · brand (identity & org)

CREATE TABLE dev_user (
  id              text PRIMARY KEY,        -- don:identity:devu/...
  display_name    text NOT NULL,
  email           citext UNIQUE NOT NULL,  -- owner-resolution join key
  role            text,
  state           text DEFAULT 'active',   -- keep deactivated for historical refs
  external_ref    text);

CREATE TABLE "group" (
  id              text PRIMARY KEY,
  name            text NOT NULL);         -- membership primitive

CREATE TABLE group_member (       -- devuser ↔ group (M2M)
  group_id        text REFERENCES "group"(id),
  dev_user_id     text REFERENCES dev_user(id),
  PRIMARY KEY(group_id, dev_user_id));

CREATE TABLE brand (
  id              text PRIMARY KEY,
  name            text NOT NULL,
  external_ref    text);              -- SFDC Division / HS Business Unit

CREATE TABLE team (
  id              text PRIMARY KEY,
  name            text NOT NULL);

CREATE TABLE group_assignment (   -- group → brand and/or team
  group_id        text REFERENCES "group"(id),
  brand_id        text REFERENCES brand(id),
  team_id         text REFERENCES team(id));

account · contact · lead

CREATE TABLE account (
  id              text PRIMARY KEY,
  subtype         text,                    -- Customer/Partner/Prospect (record-type)
  name            text NOT NULL,
  domain          text,
  phone           text,
  industry        text,
  annual_revenue  money,
  employee_count  int,
  billing_addr    jsonb,                   -- {street,city,state,zip,country}
  tier            text,
  parent_id       text REFERENCES account(id),  -- self-ref; load parents first
  owner_id        text REFERENCES dev_user(id),
  rev_org_id      text,                    -- paired Rev Org
  external_ref    text UNIQUE,
  created_at      timestamptz, updated_at timestamptz);

CREATE TABLE contact (
  id              text PRIMARY KEY,
  subtype         text,
  first_name      text, last_name text,
  email           citext,                  -- dedup/merge key
  phone_numbers   text[],
  job_title       text,
  account_id      text REFERENCES account(id),  -- must resolve or orphan
  lead_source     text,
  lifecycle_stage text,
  email_opt_out   bool DEFAULT false,   -- consent — must migrate
  mailing_addr    jsonb,
  owner_id        text REFERENCES dev_user(id),
  external_ref    text UNIQUE,
  created_at      timestamptz, updated_at timestamptz);

CREATE TABLE lead (
  id              text PRIMARY KEY,
  subtype         text,                    -- Inbound/Outbound
  name            text,
  company         text,                    -- FREE TEXT (no account pre-convert)
  email           citext, phone text,
  status          text,                    -- stage lifecycle
  source          text, score int,
  owner_id        text REFERENCES dev_user(id),
  is_converted    bool DEFAULT false,
  converted_account_id     text REFERENCES account(id),
  converted_contact_id     text REFERENCES contact(id),
  converted_opportunity_id text,          -- set on convert
  external_uid    text UNIQUE);

opportunity · product · line_item

CREATE TABLE opportunity (
  id              text PRIMARY KEY,
  subtype         text,                    -- New-Business/Renewal/Upsell
  name            text NOT NULL,
  account_id      text REFERENCES account(id),
  primary_contact_id text REFERENCES contact(id),
  amount          money,
  stage           text NOT NULL,            -- stage diagram, 1:1 map
  pipeline        text,
  probability     numeric(5,2),
  close_date      date,                    -- optionalTs on load
  forecast_category text,
  owner_id        text REFERENCES dev_user(id),
  external_uid    text UNIQUE,
  created_at      timestamptz, updated_at timestamptz);

CREATE TABLE product (               -- sales catalog, NOT DevRev Part
  id              text PRIMARY KEY,
  subtype         text,                    -- Good/Service/Subscription
  name            text NOT NULL,
  sku             text,
  unit_price      money,
  family          text, is_active bool DEFAULT true,
  external_uid    text UNIQUE);

CREATE TABLE line_item (             -- opp/quote/order child
  id              text PRIMARY KEY,
  parent_type     object_kind, parent_id text,  -- opp/quote/order
  product_id      text REFERENCES product(id),
  quantity        numeric, unit_price money, total money);

activity — polymorphic (task / call / meeting / email / note)

CREATE TABLE activity (
  id              text PRIMARY KEY,
  type            text NOT NULL,            -- task|call|meeting|email|note
  subtype         text,                    -- demo/discovery/follow-up
  title           text,
  body            text,                    -- rich_text
  status          text,                    -- open/in_progress/completed
  due_date        timestamptz,
  owner_id        text REFERENCES dev_user(id),
  -- call-specific
  direction       text, duration_sec int, disposition text,
  recording_artifact_id text,
  external_ref    text UNIQUE,
  created_at      timestamptz, updated_at timestamptz);

-- ★ polymorphic link: one activity → many objects (WhoId/WhatId analog)
CREATE TABLE activity_link (
  activity_id     text REFERENCES activity(id) ON DELETE CASCADE,
  target_type     object_kind NOT NULL,     -- account/contact/lead/opp/ticket/issue…
  target_id       text NOT NULL,
  role            text,                    -- who vs what; decision-maker etc
  PRIMARY KEY(activity_id, target_type, target_id));
CREATE INDEX ON activity_link (target_type, target_id);  -- "all activity on X"

template — one store, channel/vendor-differentiated

CREATE TABLE template (
  id              text PRIMARY KEY,
  subtype         channel NOT NULL,         -- email|sms|whatsapp|voice
  name            text NOT NULL,
  provider        text,                    -- Twilio/MSG91/Gupshup/Meta…
  profile_id      text,                    -- sender/WABA/number profile
  language        text,
  body            text,                    -- text/HTML
  variables       jsonb,                   -- merge tokens
  approval_status approval_status DEFAULT 'draft',   -- synced from provider
  external_template_id text,             -- provider id = sync key
  -- channel-differentiated (nullable per subtype)
  subject         text,                    -- email
  from_addr       text, reply_to text,      -- email
  hsm_category    text,                    -- whatsapp: MARKETING/UTILITY/AUTH
  components      jsonb,                   -- whatsapp: header/footer/buttons
  quality_rating  text,                    -- whatsapp: green/yellow/red
  dlt_template_id text, dlt_entity_id text, sender_id text,  -- sms India
  us_campaign_id  text,                    -- sms 10DLC
  ivr_flow        jsonb);                  -- voice

channel_profile — multi-profile per channel/provider

CREATE TABLE channel_profile (
  id              text PRIMARY KEY,
  channel         channel NOT NULL,
  provider        text NOT NULL,
  sender_identity text,                    -- number / short code / WABA / from-email
  keyring_id      text NOT NULL,           -- creds in developer_keyrings, NOT here
  brand_id        text REFERENCES brand(id),
  team_id         text REFERENCES team(id),
  is_active       bool DEFAULT true);

-- message/call log rows reference channel_profile + link via activity_link
On-platform mapping. (1) Each table = a leaf type via schemas.custom.set; each column = a custom_schema_fragment field of the DevRev type in the map above. (2) external_ref/external_uid UNIQUE → idempotent AirSync upserts; native identity objects (account/contact/devuser) map to stock Rev/Dev objects, the rest are custom leaf types. (3) money composite degrades to int minor-units + currency enum until native money exists. (4) activity_link = the polymorphic workaround (join leaf); index (target_type,target_id) powers "all activity on this account". (5) Secrets in developer_keyrings, referenced by id — never in channel_profile. (6) Address: flatten to fields for filtering (jsonb shown for brevity). (7) subtype = record-type on every object. This loads over AirSync as-is; the two STD-NEEDED primitives would remove the workarounds.