> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withampersand.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Write actions

A write action writes data to your customer's SaaS whenever you make an API request to us. This page focuses on writing data to existing fields. If you are looking to create new custom fields prior to writing data, please see [Create or update custom fields](/manage-customer-schemas#create-or-update-custom-fields).

## Defining writes

To define a write action, add `write` as a key in your integration defined in `amp.yaml`, and add a list of standard and custom objects you want to write to.

```YAML theme={null}
specVersion: 1.0.0
integrations:
  - name: write-to-salesforce
    displayName: Write to Salesforce
    provider: salesforce
    write:
      objects:
        - objectName: account
        - objectName: contact
        - objectName: touchpoints__c # You can include custom objects
```

### Share mappings with read actions

If you are using Read Actions and have already defined [field mappings](/read-actions#field-mappings), your Write Actions can automatically use the same mappings if you add `inheritMapping` to your manifest file. When you call our Write API, we will ensure that we are writing back to the appropriate field that the customer has mapped.

```YAML theme={null}
specVersion: 1.0.0
integrations:
  - name: write-to-salesforce
    displayName: Write to Salesforce
    provider: salesforce
    write:
      objects:
        - objectName: account
          inheritMapping: true
```

## Writing records

Once your users install an integration with a write action, your app can write data to their SaaS by making a POST call to Ampersand, the URL is in the format of:

`https://write.withampersand.com/v1/projects/:projectIdOrName/integrations/:integrationId/objects/:objectName`

You can find your project ID and integration ID in the [Ampersand Dashboard](https://dashboard.withampersand.com) (look in the address bar) or you can run the following in the terminal to get `projectId` & `integrationId` :

```bash theme={null}
# Get project ID
amp list:projects

# List integrations for project
amp list:integrations --project <PROJECT_ID>
```

`objectName` refers to the `objectName` key within the `amp.yaml` file that defines your integration. This must match the name of an object that exists within the SaaS instance.

### Create a new record

To create a new record, make a request to the [Write endpoint](/reference/write/write-records) with `type` being `create`. For example:

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/66438162-5299-4669-a41d-85c5a3b1a83e/integrations/113e9685-9a51-42cc-8662-9d9725b17f14/objects/contact' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "demo-group-id",
    "type": "create",
    "record": {
        "firstname": "Harry",
        "lastname": "Potter"
    }
}'
```

#### Create associations when creating a record

If you'd like to create records that are associated with other records, you can make a similar request with the `associations` parameter.

<Info> This is currently only supported for HubSpot. </Info>

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/<YOUR_PROJECT_ID_OR_NAME>/integrations/<INTEGRATION_ID>/objects/contact' \
--header 'X-Api-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "<GROUP_REF>",
    "type": "create",
    "record": {
        "firstname": "Harry",
        "lastname": "Potter",
        "email": "harry@hogwarts.com",
        "phone": "123456789"
    },
    "associations": [{
        "to": {"id": "18417469280"},
        "types": [{
            "associationCategory": "HUBSPOT_DEFINED",
            "associationTypeId": 279
        }]
    }]
}'
```

### Update an existing record

To update an existing record, you need to know the ID of the record, which is the ID that the SaaS provider uses to uniquely identify this record. If you created the record using Ampersand, this ID is available in the API response. If you are reading the record first using Ampersand's Read Actions, make sure you add the ID as a required field in the read action. Here is an example request:

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/66438162-5299-4669-a41d-85c5a3b1a83e/integrations/113e9685-9a51-42cc-8662-9d9725b17f14/objects/contact' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "demo-group-id",
    "type": "update",
    "record": {
        "id": "20scv09wer3klj",
        "firstname": "Harry",
        "lastname": "Potter"
    }
}'
```

### Delete a record

<Info>
  Deletion must be enabled for the object in the installation config. See [Enabling deletion for an object](#enabling-deletion-for-an-object) to enable deletion when creating or updating an installation.

  This is currently supported for HubSpot and Salesforce.
</Info>

To delete a record, call the [Write endpoint](/reference/write/write-records) with `type` set to `delete`. Provide the record `id` in the `record` object; no other fields are required. Delete operations are **synchronous only** and support **single-record** deletes.

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/<PROJECT_ID>/integrations/<INTEGRATION_ID>/objects/<OBJECT_NAME>' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "demo-group-id",
    "type": "delete",
    "record": {
        "id": "20scv09wer3klj"
    }
}'
```

### Pass additional headers

If you need to pass additional headers when creating, updating, or deleting records for a provider, you can include those headers directly in the request body when calling the write endpoint. Ampersand will forward those headers as part of the outbound request to the provider.

<Info>This is currently only supported for Salesforce. </Info>

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/<YOUR_PROJECT_ID_OR_NAME>/integrations/<INTEGRATION_ID>/objects/contact' \
--header 'X-Api-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "<GROUP_REF>",
    "type": "create",
    "record": {
        "firstname": "Harry",
        "lastname": "Potter",
        "email": "harry@hogwarts.com",
        "phone": "123456789"
    },
    "headers": [
      {
        "key": "header-key1",
        "value": "header-value1"
      },
      {
        "key": "header-key2",
        "value": "header-value2"
      }
    ]
}'
```

### Batch write

Batch write allows you to create or update multiple records in a single API call. This is useful when you need to write large amounts of data efficiently. You can include 1-100 records per batch.

<Info>Batch write is currently only supported for HubSpot and Salesforce.</Info>

To perform a batch write, use the same [write endpoint](/reference/write/write-records) but provide a `batch` array instead of a single `record`:

`https://write.withampersand.com/v1/projects/:projectIdOrName/integrations/:integrationId/objects/:objectName`

Batch writes support both [synchronous and asynchronous modes](#write-modes). By default, batch writes use **partial success**, i.e., successful records will be committed even if some records in the batch fail. To fail the entire batch when any record fails, set [`batchPolicy.allOrNone`](/reference/write/write-records) to `true` in your request.

<Info>HubSpot batch updates always result in partial success due to a provider limitation, regardless of the `allOrNone` setting.</Info>

#### Batch create

To create multiple records in a batch, set `type` to `create`:

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/<PROJECT_ID>/integrations/<INTEGRATION_ID>/objects/contact' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "customer-123",
    "type": "create",
    "mode": "synchronous",
    "batch": [
        {
            "record": {
                "firstname": "Harry",
                "lastname": "Potter",
                "email": "harry@hogwarts.com"
            }
        },
        {
            "record": {
                "firstname": "Hermione",
                "lastname": "Granger",
                "email": "hermione@hogwarts.com"
            }
        }
    ]
}'
```

#### Batch update

To update multiple records in a batch, set `type` to `update` and include the record IDs:

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/<PROJECT_ID>/integrations/<INTEGRATION_ID>/objects/contact' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "customer-123",
    "type": "update",
    "mode": "synchronous",
    "batch": [
        {
            "record": {
                "id": "20scv09wer3klj",
                "firstname": "Harry",
                "lastname": "Potter-Weasley"
            }
        },
        {
            "record": {
                "id": "30abc12xyz4mno",
                "firstname": "Hermione",
                "lastname": "Granger-Weasley"
            }
        }
    ]
}'
```

#### Batch write with associations

You can create records with associations in batch mode. Each record in the batch can have its own associations.

<Info>Associations in batch write are currently only supported for HubSpot.</Info>

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/<PROJECT_ID>/integrations/<INTEGRATION_ID>/objects/contact' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "customer-123",
    "type": "create",
    "mode": "synchronous",
    "batch": [
        {
            "record": {
                "firstname": "Harry",
                "lastname": "Potter",
                "email": "harry@hogwarts.com"
            },
            "associations": [{
                "to": {"id": "18417469280"},
                "types": [{
                    "associationCategory": "HUBSPOT_DEFINED",
                    "associationTypeId": 279
                }]
            }]
        },
        {
            "record": {
                "firstname": "Hermione",
                "lastname": "Granger",
                "email": "hermione@hogwarts.com"
            },
            "associations": [{
                "to": {"id": "18417469281"},
                "types": [{
                    "associationCategory": "HUBSPOT_DEFINED",
                    "associationTypeId": 279
                }]
            }]
        }
    ]
}'
```

### Auto batching

Ampersand can automatically group multiple single-record write requests together and executes them as a batch operation. This is useful when you want to reduce the amount of API quota that you are consuming in your customer's SaaS instance.

<Info>Auto batching is currently only available for asynchronous mode and single-record writes (not batch writes).</Info>

When you enable auto batching, Ampersand will:

1. **Accumulate records**: Multiple single-record write requests for the same installation, object, and write type (create or update) are automatically grouped together.
2. **Execute when ready**: The batch executes when either:
   * **100 records** have been accumulated, or
   * **Maximum delay time** has been reached (executes even if current batch has fewer than 100 records)

Each batch uses the max delay deadline set by the first record in that batch. This ensures records don't wait indefinitely.

#### Configuration

The auto batch policy has two configuration options:

* **`maxDelayMinutes`** (required): The maximum time to wait before executing the batch, even if it hasn't reached 100 records. Minimum: 1, Maximum: 1440 (24 hours).
* **`retryDeadlineHours`** (optional): The maximum time to keep retrying the batch write operation if it fails due to a retryable error. If not specified, defaults to 1 hour. Maximum allowed is 48 hours.

To enable auto batching, include `autoBatchPolicy` in the [write API](/reference/write/write-records) request, here's an example:

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/<PROJECT_ID>/integrations/<INTEGRATION_ID>/objects/contact' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "customer-123",
    "type": "create",
    "mode": "asynchronous",
    "autoBatchPolicy": {
        "maxDelayMinutes": 5,
        "retryDeadlineHours": 2
    },
    "record": {
        "firstname": "Alice",
        "lastname": "Wonderland",
        "email": "alice@example.com"
    }
}'
```

In this example:

* Ampersand waits for up to **5 minutes** for additional records to come in before sending the current batch.
* If 100 records accumulate before 5 minutes, the batch executes immediately.
* If the batch write fails due to a retryable error, Ampersand will retry for up to **2 hours**.

#### Monitor auto batch operations

You can monitor auto batch operations using the operation ID returned in the response. The operation ID represents the master operation for the batch. Use the [Get Operation endpoint](/reference/operation/get-an-operation) to check the status:

```bash theme={null}
curl --location 'https://api.withampersand.com/v1/projects/<PROJECT_ID>/operations/<OPERATION_ID>' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY'
```

The operation status will show:

* `in_progress`: The batch is accumulating records or executing.
* `success`: The batch write completed successfully (may include partial success with some failed records).
* `failure`: The batch write failed.

## Write modes

Write actions support two modes: `synchronous` and `asynchronous`. The mode determines how the API handles your write request and when you receive a response. These modes apply to both single record writes and batch writes. **Delete operations are always synchronous** and do not use the `mode` field.

### Synchronous mode

In synchronous mode, the write operation is processed immediately and the API waits for the operation to complete before returning a response.

For **single record writes**, the response includes the created/updated record immediately. If the write operation fails, the API will return the error response.

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/<PROJECT_ID>/integrations/<INTEGRATION_ID>/objects/contact' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "customer-123",
    "type": "create",
    "mode": "synchronous",
    "record": {
        "firstname": "John",
        "lastname": "Doe",
        "email": "john@example.com"
    }
}'
```

### Asynchronous mode

In asynchronous mode, Ampersand validates the request and immediately returns an operation ID. The write is then processed in the background. This is recommended for large batch writes or when you don't need to wait for the result.

If the write fails due to API quota issues or other retryable errors, Ampersand will automatically keep retrying for up to 1 hour. If needed, the [retry policy can be configured](#retry-policy-for-async-writes) to retry for up to 48 hours.

For single-record writes, you can also use the [auto batch policy](#auto-batching) to automatically group multiple requests together and execute them as a batch.

To use async mode, set `mode: "asynchronous"` in your request:

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/<PROJECT_ID>/integrations/<INTEGRATION_ID>/objects/contact' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "customer-123",
    "type": "create",
    "mode": "asynchronous",
    "record": {
        "firstname": "Jane",
        "lastname": "Smith"
    }
}'
```

The response returns an operation ID immediately:

```json theme={null}
{
    "operationId": "3efc0f0f-4bb9-498f-996c-9893d98ca4b5"
}
```

### Checking status of an async write operation

To check the status of an async write operation, use the [Get Operation endpoint](/reference/operation/get-an-operation) with the operation ID in the response body of the write API endpoint.

```bash theme={null}
curl --location 'https://api.withampersand.com/v1/projects/<PROJECT_ID>/operations/<OPERATION_ID>' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY'
```

The response includes the operation status and result:

```json theme={null}
{
    "id": "<OPERATION_ID>",
    "projectId": "<PROJECT_ID>",
    "integrationId": "<INTEGRATION_ID>",
    "installationId": "<INSTALLATION_ID>",
    "configId": "<CONFIG_ID>",
    "actionType": "write",
    "status": "failure",
    "result": "[contacts] Error writing to provider",
    "createTime": "2025-08-26T23:19:27.616204Z",
    "updateTime": "2025-08-26T23:19:28.424859Z",
    "resource": "contacts"
}
```

An operation can be in a `success`, `failure` or `in_progress` state.

### Retry policy for async writes

Ampersand will automatically retry failed operations with exponential backoff until the default deadline of 1 hour. If this is not enough, you can specify a longer deadline of up to 48 hours.

```bash theme={null}
curl --location 'https://write.withampersand.com/v1/projects/<PROJECT_ID>/integrations/<INTEGRATION_ID>/objects/contact' \
--header 'X-Api-Key: YOUR_AMPERSAND_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "groupRef": "customer-123",
    "type": "create",
    "mode": "asynchronous",
    "retryPolicy": {
        "deadlineHours": 24
    },
    "record": {
        "firstname": "Bob",
        "lastname": "Johnson"
    }
}'
```

Retry policies only apply to asynchronous mode.

## Advanced use cases

<Info>These advanced use cases are currently only supported when creating or updating installations via the API or [Headless UI](/headless).</Info>

### Enabling deletion for an object

By default, delete operations are disabled. To enable deletion, set `deletionSettings.enabled` to `true` for the object when creating or updating an installation.

#### Using headless UI

You can enable or disable deletion on a customer-by-customer basis using `setEnableDeletion` and `setDisableDeletion` from `config.writeObject()`. Learn more about [pre-requisites for headless UI here](/headless#prerequisites).

```typescript theme={null}
function WriteObjectConfig() {
  const config = useLocalConfig();
  const contactWriteConfig = config.writeObject("Contact");

  // Enable deletion for the Contact object
  contactWriteConfig.setEnableDeletion();

  // Or disable deletion
  contactWriteConfig.setDisableDeletion();
}
```

#### Using API

You can use these APIs to set `deletionSettings.enabled`:

<AccordionGroup>
  <Accordion title="Create Installation">
    [Create Installation API](/reference/installation/create-a-new-installation) — Include `deletionSettings` in the write object when creating the installation:

    ```Javascript theme={null}
    {
      "groupRef": "customer-group-ref",
      "connectionId": "connection-id",
      "config": {
        "revisionId": "revision-id",
        "content": {
          "provider": "salesforce",
          "write": {
            "objects": {
              "contact": {
                "objectName": "contact",
                "deletionSettings": {
                  "enabled": true
                }
              }
            }
          }
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Update Installation">
    [Update Installation API](/reference/installation/update-an-installation) — PATCH the installation with the write object config and an update mask that includes the object path:

    ```Javascript theme={null}
    {
      "updateMask": ["config.content.write.objects.contact"],
      "installation": {
        "config": {
          "content": {
            "provider": "salesforce",
            "write": {
              "objects": {
                "contact": {
                  "objectName": "contact",
                  "deletionSettings": {
                    "enabled": true
                  }
                }
              }
            }
          }
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Update Installation Object (JSON Patch)">
    [Update Installation Object API](/reference/installation/update-an-installation-object) — PATCH the object's config content with a JSON Patch operation. Use this to change only this object without sending the full installation config:

    ```Javascript theme={null}
    {
      "groupRef": "customer-group-ref",
      "action": "write",
      "changes": [
        {
          "op": "add",
          "path": "/deletionSettings",
          "value": { "enabled": true }
        }
      ]
    }
    ```

    Use `"op": "replace"` if the object already has `deletionSettings` and you are changing it.
  </Accordion>
</AccordionGroup>

### Prevent overwriting of customer data

You can control whether a field is included in write requests. For example, you can avoid overwriting data the customer has changed, or leave a field blank on create and write to it only on update.

**`writeOnCreate`** — whether to write the field when creating a record:

* **always** (default): Always include the field on create
* **never**: Never include the field on create

**`writeOnUpdate`** — whether to write the field when updating a record:

* **always** (default): Always include the field on update
* **never**: Never include the field on update (avoids overwriting customer changes)
* **ifEmpty**: Only include the field if it's currently empty. Supported for Salesforce and HubSpot only.

**Example configurations:**

* Only set a field on create, never overwrite on update → `writeOnCreate: "always"`, `writeOnUpdate: "never"`
* Leave field blank on create, only fill on update → `writeOnCreate: "never"`, `writeOnUpdate: "always"`
* Fill on update only when the field is empty → `writeOnUpdate: "ifEmpty"`
* Never write to the field for this customer → `writeOnCreate: "never"`, `writeOnUpdate: "never"`

#### Using headless UI

You can set overwrite behavior on a customer-by-customer basis using the function `setFieldSettings`. Learn more about [pre-requisites for headless UI here](/headless#prerequisites).

```typescript theme={null}
function WriteObjectConfig() {
  const config = useLocalConfig();
  const contactWriteConfig = config.writeObject("Contact");

  // Only write to source field when creating a record, not when updating
  contactWriteConfig.setFieldSettings({
    fieldName: "source",
    settings: {
      writeOnCreate: "always",  // Write to source field when creating a record
      writeOnUpdate: "never"    // Do not write to source field when updating a record
    }
  });
}
```

#### Using API

As an alternative to headless UI, you can set overwrite behavior on a customer-by-customer basis when you make a call to the [Create Installation API](/reference/installation/create-a-new-installation). Here is a sample request body:

```Javascript theme={null}
{
  "groupRef": "customer-group-ref",
  "connectionId": "connection-id",
  "config": {
    "revisionId": "revision-id",
    "content": {
      "provider": "hubspot",
      "write": {
        "objects": {
          "lead": {
            "objectName": "lead",
            "selectedFieldSettings": {
              "source": {
                  // Do not write to source field when updating a record.
                  "writeOnUpdate": "never",
                  // Write to source field when creating a record,
                  // This is the default behavior.
                  "writeOnCreate": "always"
              },
            },
          }
        }
      },
    }
  }
}
```

### Default values

Default values are applied to a field whenever that value is missing from the write request. We currently support string, boolean and number values.

#### Using headless UI

You can set default values on a customer-by-customer basis using the `setFieldSettings` function. Learn more about [pre-requisites for headless UI here](/headless#prerequisites).

```typescript theme={null}
function WriteObjectConfig() {
  const config = useLocalConfig();
  const contactWriteConfig = config.writeObject("Contact");

  // Set default values for fields
  contactWriteConfig.setFieldSettings({
    fieldName: "source",
    settings: {
      default: {
        stringValue: "myApp"  // Set source field to "myApp" by default
      }
    }
  });

  contactWriteConfig.setFieldSettings({
    fieldName: "amount",
    settings: {
      default: {
        integerValue: 0  // Set amount field to 0 by default
      }
    }
  });

  contactWriteConfig.setFieldSettings({
    fieldName: "automated",
    settings: {
      default: {
        booleanValue: true  // Set automated field to true by default
      }
    }
  });
}
```

#### Using API

As an alternative to headless UI, you can set this behavior on a customer-by-customer basis when you make a call to the [Create Installation API](/reference/installation/create-a-new-installation). Here is a sample request body:

```Javascript theme={null}
{
  "groupRef": "customer-group-ref",
  "connectionId": "connection-id",
  "config": {
    "revisionId": "revision-id",
    "content": {
      "provider": "hubspot",
      "write": {
        "objects": {
          "lead": {
            "objectName": "lead",
            "selectedFieldSettings": {
              "source": {
                "default": {
                  "stringValue": "myApp" // Set the source field to myApp
                },
              },
              "amount": {
                "default": {
                  "integerValue": 0 // Set the amount field to 0
                },
              },
              "automated": {
                "default": {
                  "booleanValue": true // Set the automated field to true
                },
              },
            },
          }
        }
      },
    }
  }
}
```

### Remove unmapped fields

If your write action is [sharing mapping with read actions](#share-mappings-with-read-actions), we can drop unmapped fields from the write request before sending the request to the provider API. Please contact [support@withampersand.com](mailto:support@withampersand.com) if you want to use this preview feature.

### Combine use cases

Here is a full example for a request body to the [Create Installation API](/reference/installation/create-a-new-installation) that combines multiple use cases:

* sharing mapping with read actions
* applying default values
* configuring when the field should be written to

#### Using headless UI

You can see an [example here of combining multiple use cases](/headless#configure-features-together).

#### Using API

```Javascript theme={null}
{
  "groupRef": "customer-group-ref",
  "connectionId": "connection-id",
  "config": {
    "revisionId": "revision-id",
    "content": {
      "provider": "hubspot",
      "write": {
        "objects": {
          "lead": {
            "objectName": "lead",
            "inheritMapping": true, // Inherit mapping from read actions
            "selectedFieldSettings": {
              "customSource": { // customSource is a mapped field
                  "default": {
                    "stringValue": "myApp" // Use "mapApp" as default value
                  },
                  // Do not write to the field when updating a record.
                  "writeOnUpdate": "never",
                  // Write to the field on creating a record,
                  // This is the default behavior.
                  "writeOnCreate": "always"
              },
            },
          }
        }
      },
      "read": {
        "objects": {
          "lead": {
            "objectName": "lead",
            "destination": "myWebhook",
            "selectedFieldMappings": {
              "customSource": "myCustomSource"
            }
          }
      }
    }
  }
}
```

For this installation, `customSource` has been mapped to the `myCustomSource` field in this customer's Hubspot instance, the default value for this field is "myApp" and the field will only be written to when it is a create record request, and not an update request.
