curl --request POST \
--url http://localhost:3500/v1/integrations/{id}/sync-estimates \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{}'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({})
};
fetch('http://localhost:3500/v1/integrations/{id}/sync-estimates', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "http://localhost:3500/v1/integrations/{id}/sync-estimates"
payload = {}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"integration_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "running",
"period": {
"from": "2023-11-07T05:31:56Z",
"to": "2023-11-07T05:31:56Z"
},
"scope": {
"suppliers": {
"mode": "all",
"items": [
{
"id": "<string>",
"name": "<string>",
"code": "<string>",
"group": {
"kind": "vendor-sites",
"prefixes": [
"<string>"
],
"site_count": 123,
"inactive_site_count": 1
}
}
],
"include_unidentified": true
},
"companies": [
{
"id": "<string>",
"name": "<string>",
"country_code": "<string>",
"accounting_currency_code": "<string>"
}
],
"processing_statuses": [
"<string>"
],
"path_globs": [
"<string>"
],
"module_company_codes": {
"costinvoice": [
"<string>"
],
"purchaseorder": [
"<string>"
]
}
},
"invoice_count": 123,
"lower_bound": true,
"configuration_revision_matches": true,
"created_at": "2023-11-07T05:31:56Z",
"finished_at": "2023-11-07T05:31:56Z"
}{
"error": {
"code": "validation_error",
"message": "Invalid request format, parameters, or body. Details contain up to 20 actionable field errors; the complete encoded request body must be at most 2 MiB (2,097,152 bytes).",
"request_id": "req_example"
}
}{
"error": {
"code": "invalid_token",
"message": "Missing or invalid bearer credential.",
"request_id": "req_example"
}
}{
"error": {
"code": "forbidden",
"message": "Access denied: forbidden, token_disabled, organization_required, insufficient_role, or mfa_required. Check the error code and effective permissions.",
"request_id": "req_example"
}
}{
"error": {
"code": "not_found",
"message": "The resource does not exist in the current organization.",
"request_id": "req_example"
}
}{
"error": {
"code": "estimate_in_progress",
"message": "An estimate is already running, a sync is running (workflow_run_id names it), the integration is disabled or not tested, or the provider does not support estimates.",
"request_id": "req_example"
}
}{
"error": {
"code": "rate_limit_exceeded",
"message": "The IP or authenticated credential exceeded its request limit.",
"request_id": "req_example"
}
}{
"error": {
"code": "internal_error",
"message": "Unexpected server failure. Include the request ID when contacting support.",
"request_id": "req_example"
}
}{
"error": {
"code": "service_unavailable",
"message": "The estimate could not be started. Nothing is running; try again.",
"request_id": "req_example"
}
}Estimate a sync
Counts the invoices a sync of this period and scope would import, without importing anything. Takes the same period and scope as starting a sync and applies the same scope rule, so the same body counts exactly the invoices the sync would read. Only providers with capabilities.estimate support it. One estimate runs per integration: while one is running, starting another is 409 estimate_in_progress. Find it with GET /v1/integrations//sync-estimates?limit=1, and wait for it or cancel it. It stops at a time limit and then reports a lower bound. Poll the estimate until it is no longer running.
curl --request POST \
--url http://localhost:3500/v1/integrations/{id}/sync-estimates \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{}'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({})
};
fetch('http://localhost:3500/v1/integrations/{id}/sync-estimates', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "http://localhost:3500/v1/integrations/{id}/sync-estimates"
payload = {}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"integration_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "running",
"period": {
"from": "2023-11-07T05:31:56Z",
"to": "2023-11-07T05:31:56Z"
},
"scope": {
"suppliers": {
"mode": "all",
"items": [
{
"id": "<string>",
"name": "<string>",
"code": "<string>",
"group": {
"kind": "vendor-sites",
"prefixes": [
"<string>"
],
"site_count": 123,
"inactive_site_count": 1
}
}
],
"include_unidentified": true
},
"companies": [
{
"id": "<string>",
"name": "<string>",
"country_code": "<string>",
"accounting_currency_code": "<string>"
}
],
"processing_statuses": [
"<string>"
],
"path_globs": [
"<string>"
],
"module_company_codes": {
"costinvoice": [
"<string>"
],
"purchaseorder": [
"<string>"
]
}
},
"invoice_count": 123,
"lower_bound": true,
"configuration_revision_matches": true,
"created_at": "2023-11-07T05:31:56Z",
"finished_at": "2023-11-07T05:31:56Z"
}{
"error": {
"code": "validation_error",
"message": "Invalid request format, parameters, or body. Details contain up to 20 actionable field errors; the complete encoded request body must be at most 2 MiB (2,097,152 bytes).",
"request_id": "req_example"
}
}{
"error": {
"code": "invalid_token",
"message": "Missing or invalid bearer credential.",
"request_id": "req_example"
}
}{
"error": {
"code": "forbidden",
"message": "Access denied: forbidden, token_disabled, organization_required, insufficient_role, or mfa_required. Check the error code and effective permissions.",
"request_id": "req_example"
}
}{
"error": {
"code": "not_found",
"message": "The resource does not exist in the current organization.",
"request_id": "req_example"
}
}{
"error": {
"code": "estimate_in_progress",
"message": "An estimate is already running, a sync is running (workflow_run_id names it), the integration is disabled or not tested, or the provider does not support estimates.",
"request_id": "req_example"
}
}{
"error": {
"code": "rate_limit_exceeded",
"message": "The IP or authenticated credential exceeded its request limit.",
"request_id": "req_example"
}
}{
"error": {
"code": "internal_error",
"message": "Unexpected server failure. Include the request ID when contacting support.",
"request_id": "req_example"
}
}{
"error": {
"code": "service_unavailable",
"message": "The estimate could not be started. Nothing is running; try again.",
"request_id": "req_example"
}
}Authorizations
Personal API key. Send X-Organization-Id. The required cumulative level is listed in x-watchdog-permission.
Headers
Required for personal API keys. Target one organization you have access to. Migrated keys may omit it to use their original organization. For Clerk sessions, it must match the active organization.
Path Parameters
Body
First invoice date to read, inclusive.
^\d{4}-\d{2}-\d{2}$Last invoice date to read, inclusive.
^\d{4}-\d{2}-\d{2}$Omit to read the integration’s saved scope. When sent, it replaces the saved scope for this run: every dimension it leaves out reads everything instead of falling back to the saved value.
Show child attributes
Show child attributes
Response
The estimate started.
running, completed, failed, cancelled Invoice dates counted: from is inclusive, to is exclusive.
Show child attributes
Show child attributes
The scope counted: the saved one, or the run scope applied over it.
Show child attributes
Show child attributes
Invoices a sync of this period and scope would import. Null until completed.
The count stopped at the time limit, so a sync would import at least invoice_count.
False once the integration scope or credentials changed after the estimate started; the count no longer describes what a sync would read.