# Create and query Salesforce records

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

## Overview {#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 - is created and read with the same calls, 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](https://zato.io/docs/dev/salesforce/rest-api-python.html) shows how to create one.

## Creating a record {#creating-a-record}

The service defines its input and output with [models](https://zato.io/docs/dev/model/index.html), maps the input to a plain dict with the field names Salesforce expects - custom field names end in \_\_c - and posts it:

```python
# -*- 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:

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

## Reading a record back {#reading-a-record-back}

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

```python
# -*- 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](https://zato.io/docs/dev/salesforce/soql-relationships-and-pagination.html) covers queries, relationship traversal and pagination.

## Exposing the services over REST {#exposing-the-services-over-rest}

Create a [REST channel](https://zato.io/docs/dev/rest/channels.html) for each service - for instance, /api/campaign/create pointing to crm.create-campaign with the POST method, secured with [Basic Auth](https://zato.io/docs/dev/rest/authentication.html). Your API clients authenticate with credentials they already understand while Zato deals with how to authenticate against Salesforce.

## Testing {#testing}

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

And reading it back:

```bash
$ curl http://api:password@localhost:11223/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](https://zato.io/docs/dev/salesforce/error-handling-and-api-limits.html).

## Learn more {#learn-more}

- [Salesforce integration overview](https://zato.io/integrations/salesforce/) - Start here to see how teams build Salesforce integrations
- [The REST API from Python](https://zato.io/docs/dev/salesforce/rest-api-python.html) - Connected apps, credentials, connections and the self.salesforce API
- [SOQL relationships and pagination](https://zato.io/docs/dev/salesforce/soql-relationships-and-pagination.html) - Parent traversal in the select list and the nextRecordsUrl loop
- [Upserts and external IDs](https://zato.io/docs/dev/salesforce/upserts-and-external-ids.html) - Idempotent create-or-update, keyed by your own identifiers
- [Updates and deletes](https://zato.io/docs/dev/salesforce/updates-and-deletes.html) - Diff-driven partial updates and deleting records by ID
- [Error handling and API limits](https://zato.io/docs/dev/salesforce/error-handling-and-api-limits.html) - Error arrays, failure policies and the daily request allowance
- [A CRM sync, end to end](https://zato.io/docs/dev/salesforce/crm-sync-scenario.html) - A scheduled one-way sync with correlation keys and drift detection
