Create and query Salesforce records

A REST channel that accepts JSON, maps it to a Campaign, creates the record and reads it back.

Overview

This is the canonical accept-and-transform flow: an external application sends JSON to a REST channel, a Python service maps that input to the format Salesforce expects, creates the record and returns its new ID. A second service reads a record back by its ID.

The example uses the Campaign object, but every sObject - Account, Contact, Lead, or your own custom objects - works the same way, because the REST API address space is uniform: /sobjects/{Type}/ to create, /sobjects/{Type}/{id} to read.

It assumes a connection named "My Salesforce Connection" already exists - the getting started guide shows how to create one.

Creating a record

The service defines its input and output with models, maps the input to a plain dict with the field names Salesforce expects - custom fields carry the __c suffix - and posts it:

# -*- coding: utf-8 -*-

# stdlib
from dataclasses import dataclass

# Zato
from zato.server.service import Model, Service

# ################################################################################################################################

@dataclass(init=False)
class CreateCampaignRequest(Model):
    name:    str
    segment: str

@dataclass(init=False)
class CreateCampaignResponse(Model):
    campaign_id: str

# ################################################################################################################################

class CreateCampaign(Service):
    name = 'crm.create-campaign'

    input  = CreateCampaignRequest
    output = CreateCampaignResponse

    def handle(self):

        # Map our input to the field names Salesforce expects ..
        request = {
            'Name': self.request.input.name,
            'Segment__c': self.request.input.segment,
        }

        # .. get a connection to Salesforce ..
        conn = self.salesforce['My Salesforce Connection']

        # .. create the record now ..
        sf_response = conn.post('/sobjects/Campaign/', request)

        # .. and return the new record's ID to our caller.
        response = CreateCampaignResponse()
        response.campaign_id = sf_response['id']

        self.response.payload = response

A successful create answers with the new record's ID and a success flag:

{"id": "701RO00000BpkfMYAR", "success": true, "errors": []}

Reading a record back

A GET on the record's path returns all of its fields:

# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class GetCampaign(Service):
    name = 'crm.get-campaign'

    input = 'campaign_id'

    def handle(self):

        campaign_id = self.request.input.campaign_id

        conn = self.salesforce['My Salesforce Connection']

        # Read the record by its ID ..
        campaign = conn.get(f'/sobjects/Campaign/{campaign_id}')

        # .. and pass it on as is.
        self.response.payload = campaign

To find records by their business attributes rather than by ID, use SOQL - the SOQL guide covers queries, relationship traversal and pagination.

Exposing the services over REST

Create a REST channel for each service - for instance, /api/campaign/create pointing to crm.create-campaign with the POST method, secured with Basic Auth. Your API clients authenticate with credentials they already understand while Zato deals with how to authenticate against Salesforce.

Testing

$ curl http://api:password@localhost:17010/api/campaign/create \
    -d '{"name":"Summer promotion", "segment":"Enterprise customers"}'
{"campaign_id":"701RO00000BpkfMYAR"}
$

And reading it back:

$ curl http://api:password@localhost:17010/api/campaign/get \
    -d '{"campaign_id":"701RO00000BpkfMYAR"}'
{"Id":"701RO00000BpkfMYAR", "Name":"Summer promotion", "Segment__c":"Enterprise customers"}
$

What to do when a create fails - a missing required field, an invalid picklist value - is covered in the error handling guide.

Learn more