> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.tabs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.tabs.com/_mcp/server.

# Pull records from your ERP

POST https://integrators.prod.api.tabsplatform.com/v3/erp/sync
Content-Type: application/json

Starts pulling records from your connected ERP into Tabs. This is the same pull the Sync button in your dashboard starts. You don't pass an ERP or an account: Tabs uses the ERP connected to the account your API key belongs to.

**What gets pulled**

* With no body, every entity type your ERP supports (listed below).
* With `entityTypes`, only those types, for example `["CUSTOMER", "ITEM"]`. Each one must be in your ERP's list.
* For each entity type, the pull fetches records created or changed in your ERP since the last successful pull of that type, going back at least 24 hours. The first pull of a type fetches every record.
* If you have never pulled from your ERP, run one pull with no body before pulling single entity types. Items and customers can't be saved until the accounts, currencies, departments, locations and ERP settings (`METADATA`) they refer to have been pulled.

**Entity types each ERP accepts**

* QuickBooks: `ACCOUNT`, `CLASS`, `DEPARTMENT`, `ITEM`, `CUSTOMER`, `INVOICE`, `PAYMENT`, `METADATA`
* NetSuite: `CURRENCY`, `ACCOUNT`, `DEPARTMENT`, `LOCATION`, `ITEM`, `CUSTOMER`, `INVOICE`, `PAYMENT`, `METADATA`
* Sage Intacct: `ACCOUNT`, `CLASS`, `DEPARTMENT`, `LOCATION`, `ITEM`, `CUSTOMER`, `PROJECT`, `ADDRESS_CONTACT`, `INVOICE`, `PAYMENT`, `METADATA`
* Campfire: `METADATA`, `ACCOUNT`, `DEPARTMENT`, `ITEM`, `CUSTOMER`, `INVOICE`, `PAYMENT`

Pulling from Rillet isn't supported.

**One pull at a time**

Only one pull runs for your account at a time. If a pull is already running, including one started from the dashboard, the request returns 409 and starts nothing. Try again once it finishes.

**Checking the result**

The pull runs in the background. Poll `GET /v3/jobs/{jobId}` with the returned `jobId`. The job's `type` is `SYNC`.

* `NEW` or `IN_PROGRESS`: the pull hasn't finished.
* `SUCCESS`: the pull finished. `results.data` holds totals (`createdCount`, `updatedCount`, `skippedCount`, `errorCount`, `totalCount`) and one entry per entity type in `results.data.entities`. If one entity type fails, the others still run and the job is still `SUCCESS`, so check `results.data.success`, and `error` on each entry in `results.data.entities`.
* `FAILURE`: the pull stopped, or never started. If it started, `results.data.error` says why. NetSuite accounts connected with OAuth 2.0 get `FAILURE` until the one-time subsidiary setup in the dashboard is finished.

Reference: https://docs.tabs.com/api-reference/erp-sync/integrators-api-erp-sync-controller-v-3-start-erp-sync

## Authentication

- `Authorization` header (required) — API Key authentication via header

## Request

### Body (application/json)

This endpoint expects a StartErpSyncDto.

- `entityTypes` (list of enum, optional) — Entity types to pull. Leave out to pull every type your ERP supports. Each type must be one your connected ERP accepts; any other returns 400 and starts nothing. - QuickBooks: `ACCOUNT`, `CLASS`, `DEPARTMENT`, `ITEM`, `CUSTOMER`, `INVOICE`, `PAYMENT`, `METADATA` - NetSuite: `CURRENCY`, `ACCOUNT`, `DEPARTMENT`, `LOCATION`, `ITEM`, `CUSTOMER`, `INVOICE`, `PAYMENT`, `METADATA` - Sage Intacct: `ACCOUNT`, `CLASS`, `DEPARTMENT`, `LOCATION`, `ITEM`, `CUSTOMER`, `PROJECT`, `ADDRESS_CONTACT`, `INVOICE`, `PAYMENT`, `METADATA` - Campfire: `METADATA`, `ACCOUNT`, `DEPARTMENT`, `ITEM`, `CUSTOMER`, `INVOICE`, `PAYMENT`
  - Allowed values: `ITEM`, `CUSTOMER`, `PAYMENT`, `INVOICE`, `SALES_ORDER`, `DEPARTMENT`, `LOCATION`, `PROJECT`, `CURRENCY`, `ACCOUNT`, `CLASS`, `CONTRACT`, `CREDIT_MEMO`, `ADDRESS_CONTACT`, `CONTACT`, `BILLING_ADDRESS`, `SHIPPING_ADDRESS`, `METADATA`, `BOOK_PERIOD`, `JOURNAL_ENTRY`, `CUSTOM_FIELD`

## Response

### 202

The pull was started

- `payload` (StartErpSyncResponseDto, required) — Response payload, will be empty when success is false
- `success` (boolean, required) — Boolean with true=success, false=failure
- `message` (string, required) — Plain-text description of the result
- `error` (IntegratorsApiError, optional) — json element with any error messages or warnings

## Errors

### 400 Bad Request Error

No ERP is connected, the connected ERP is Rillet, or `entityTypes` has an unknown type or one your ERP can't pull

- `any`

### 409 Conflict Error

A pull is already running for your account, including one started from the dashboard Sync button

- `any`

## Types

### StartErpSyncResponseDto

- `jobId` (string, required) — Job ID for this pull. Poll `GET /v3/jobs/{jobId}` until the status is `SUCCESS` or `FAILURE`. A `SUCCESS` job can still have failed entity types; check `results.data.success`.

### IntegratorsApiError

- `code` (double, required) — API response code
- `message` (string, required) — API response message
- `errorCode` (string, optional) — Machine-readable error code
- `details` (IntegratorsApiErrorDetails, optional) — Additional details about the error

### IntegratorsApiErrorDetails

Additional details about the error

## Examples

**Request**

```json
{}
```

**Response**

```json
{
  "payload": {
    "jobId": "wf-123e4567-e89b-12d3-a456-426614174000"
  },
  "success": true,
  "message": "string",
  "error": {
    "code": 1.1,
    "message": "string",
    "errorCode": "string",
    "details": {}
  }
}
```

**SDK Code**

```python
import requests

url = "https://integrators.prod.api.tabsplatform.com/v3/erp/sync"

payload = {}
headers = {
    "Authorization": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://integrators.prod.api.tabsplatform.com/v3/erp/sync';
const options = {
  method: 'POST',
  headers: {Authorization: '<apiKey>', 'Content-Type': 'application/json'},
  body: '{}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://integrators.prod.api.tabsplatform.com/v3/erp/sync"

	payload := strings.NewReader("{}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://integrators.prod.api.tabsplatform.com/v3/erp/sync")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://integrators.prod.api.tabsplatform.com/v3/erp/sync")
  .header("Authorization", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://integrators.prod.api.tabsplatform.com/v3/erp/sync', [
  'body' => '{}',
  'headers' => [
    'Authorization' => '<apiKey>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://integrators.prod.api.tabsplatform.com/v3/erp/sync");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://integrators.prod.api.tabsplatform.com/v3/erp/sync")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```