Developers
Set your API contextPick a region and space ID once. Every sample fills in your base URL.
Where do I find these?

In Coevera: open your space's API application and choose Show Access. It shows the space ID and the base URL, whose host starts with your region.

From the API: if you know the space ID, the discovery endpoint GET /api/discovery/spaces/{space_id} returns its region and base URLs, without credentials.

Only the region and space ID are kept, in this browser. Never paste a key or password here.

More in the quickstart

ConceptsBatch operations

Batch operations

Create, update, delete or restore many records in one call. By default one invalid item fails the whole batch; with rollback-method=1 a batch can succeed in part.

Answer

By default, one invalid item fails the whole batch with HTTP 422 and nothing is written; the error does not say which item failed. With rollback-method=1 the valid items are written, the batch answers HTTP 200 with success: false, and data holds one entry per item in input order: the record's ID or an error.

A batch writes many records in one request. It is faster than one request per record, and it decides what happens to the valid items when one of them is invalid: read that part before you send your first batch.

Batch endpoints

Endpoint Body Does
POST /entities/{Entity}/batch-modify An array of records Creates the items without an id, updates the items with one
POST /entities/{Entity}/batch-delete An array of IDs Soft-deletes the records
POST /entities/{Entity}/batch-restore An array of IDs Restores soft-deleted records

In GraphQL, bulkUpdate<Entity> mutations such as bulkUpdateContact create and update in one call. Every entity's batch operations are on its reference page, such as POST /entities/Contacts/batch-modify and bulkUpdateContact.

The API sets no batch size limit. Send up to 100 records per request for a quick response: a batch takes about 40 ms per record, so 500 records take about 20 s. A larger batch is not refused, but it answers more slowly, and by default one invalid item throws away more work.

Deleting a record leaves its relation records active. Deleting contacts or accounts leaves their ContactAccountRelations in place, still pointing at the deleted records, and deleting a CloudObjectRelation leaves its CloudObject, the file. Delete those on their own.

By default, all or nothing

The sample sends three contacts; the third has no owner. Sent without rollback-method, the batch fails as a whole:

# No --fail-with-body: the 422 is the expected answer here.
curl -sS -w '\nHTTP %{http_code}\n' "$COEVERA_BASE/entities/Contacts/batch-modify" \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
  -H "Content-Type: application/json" \
  -d "$CONTACTS"
const strict = await call("/entities/Contacts/batch-modify", contacts);
console.log(`Default rollback: HTTP ${strict.status}`, JSON.stringify(strict.body, null, 2));
strict = session.post(f"{BASE}/entities/Contacts/batch-modify", json=contacts, timeout=60)
print(f"Default rollback: HTTP {strict.status_code}", json.dumps(strict.json(), indent=2))
batch ROLLBACK_ALL | gql
echo
const strict = await gql(ADD_CONTACTS, { input: contacts, rollbackMethod: "ROLLBACK_ALL" });
console.log("ROLLBACK_ALL:", JSON.stringify(strict, null, 2));
strict = gql(ADD_CONTACTS, {"input": contacts, "rollbackMethod": "ROLLBACK_ALL"})
print("ROLLBACK_ALL:", json.dumps(strict, indent=2))

The API answers HTTP 422 with code 40000 ERROR_ENTITY_VALIDATION and writes nothing, not even the two valid contacts. The error carries entity_index: null and an entity_id the server made up for the new record, so it does not tell you which input item failed:

422 Unprocessable EntityRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "code": 40000,
  "name": "ERROR_ENTITY_VALIDATION",
  "message": "\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].owner' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].unit' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].owner_id' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].unit_id' is null or empty.",
  "entity_id": "0c000000-0000-4000-a000-000000000001",
  "entity_name": "Contact",
  "entity_errors": [],
  "field_errors": [
    {
      "field_id": "10000000-0000-4000-a000-000000000001",
      "field_name": "unit_id",
      "name": "ERROR_FIELD_VALIDATION",
      "code": 40002,
      "errors": [
        {
          "code": 119,
          "name": "ERROR_PARAMETER_MISSING",
          "message": "Non-null field 'Contact'[0c000000-0000-4000-a000-000000000001].unit' is null or empty.",
          "field_id": "10000000-0000-4000-a000-000000000001",
          "field_name": "unit_id"
        },
        {
          "code": 119,
          "name": "ERROR_PARAMETER_MISSING",
          "message": "Non-null field 'Contact'[0c000000-0000-4000-a000-000000000001].unit_id' is null or empty.",
          "field_id": "10000000-0000-4000-a000-000000000001",
          "field_name": "unit_id"
        }
      ]
    },
    {
      "field_id": "10000000-0000-4000-a000-000000000002",
      "field_name": "owner_id",
      "name": "ERROR_FIELD_VALIDATION",
      "code": 40002,
      "errors": [
        {
          "code": 119,
          "name": "ERROR_PARAMETER_MISSING",
          "message": "Non-null field 'Contact'[0c000000-0000-4000-a000-000000000001].owner' is null or empty.",
          "field_id": "10000000-0000-4000-a000-000000000002",
          "field_name": "owner_id"
        },
        {
          "code": 119,
          "name": "ERROR_PARAMETER_MISSING",
          "message": "Non-null field 'Contact'[0c000000-0000-4000-a000-000000000001].owner_id' is null or empty.",
          "field_id": "10000000-0000-4000-a000-000000000002",
          "field_name": "owner_id"
        }
      ]
    }
  ],
  "step_checklist_errors": [],
  "entity_index": null,
  "http_status": 422,
  "success": false
}
Show all 58 linesShow less
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "errors": [
    {
      "message": "\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].owner' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].unit' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].owner_id' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].unit_id' is null or empty.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "bulkUpdateContact"
      ],
      "api_error": {
        "code": 40000,
        "name": "ERROR_ENTITY_VALIDATION",
        "message": "\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].owner' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].unit' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].owner_id' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000001].unit_id' is null or empty.",
        "entity_id": "0c000000-0000-4000-a000-000000000001",
        "entity_name": "Contact",
        "entity_errors": [],
        "field_errors": [
          {
            "field_id": "10000000-0000-4000-a000-000000000001",
            "field_name": "unit_id",
            "name": "ERROR_FIELD_VALIDATION",
            "code": 40002,
            "errors": [
              {
                "code": 119,
                "name": "ERROR_PARAMETER_MISSING",
                "message": "Non-null field 'Contact'[0c000000-0000-4000-a000-000000000001].unit' is null or empty.",
                "field_id": "10000000-0000-4000-a000-000000000001",
                "field_name": "unit_id"
              },
              {
                "code": 119,
                "name": "ERROR_PARAMETER_MISSING",
                "message": "Non-null field 'Contact'[0c000000-0000-4000-a000-000000000001].unit_id' is null or empty.",
                "field_id": "10000000-0000-4000-a000-000000000001",
                "field_name": "unit_id"
              }
            ]
          },
          {
            "field_id": "10000000-0000-4000-a000-000000000002",
            "field_name": "owner_id",
            "name": "ERROR_FIELD_VALIDATION",
            "code": 40002,
            "errors": [
              {
                "code": 119,
                "name": "ERROR_PARAMETER_MISSING",
                "message": "Non-null field 'Contact'[0c000000-0000-4000-a000-000000000001].owner' is null or empty.",
                "field_id": "10000000-0000-4000-a000-000000000002",
                "field_name": "owner_id"
              },
              {
                "code": 119,
                "name": "ERROR_PARAMETER_MISSING",
                "message": "Non-null field 'Contact'[0c000000-0000-4000-a000-000000000001].owner_id' is null or empty.",
                "field_id": "10000000-0000-4000-a000-000000000002",
                "field_name": "owner_id"
              }
            ]
          }
        ],
        "step_checklist_errors": [],
        "entity_index": null
      }
    }
  ],
  "data": {
    "bulkUpdateContact": null
  }
}
Show all 75 linesShow less

GraphQL's default, rollbackMethod: ROLLBACK_ALL, behaves the same way, with HTTP 200, the error in errors and no result. A batch-delete with an ID that does not exist fails the same way: HTTP 404, code 143 ERROR_NO_ENTITY, and nothing is deleted.

Partial failures

The same batch sent with rollback-method=1 writes the two valid contacts and reports the third. The create an account with contacts recipe adds its contacts this way:

partial=$(curl -sS --fail-with-body "$COEVERA_BASE/entities/Contacts/batch-modify?rollback-method=1" \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
  -H "Content-Type: application/json" \
  -d "$CONTACTS")
echo "$partial"
const partial = await call("/entities/Contacts/batch-modify?rollback-method=1", contacts);
console.log(`rollback-method=1: HTTP ${partial.status}`, JSON.stringify(partial.body, null, 2));
if (partial.status !== 200) throw new Error(`Expected HTTP 200, got ${partial.status}`);
partial = session.post(
    f"{BASE}/entities/Contacts/batch-modify",
    params={"rollback-method": 1},
    json=contacts,
    timeout=60,
)
print(f"rollback-method=1: HTTP {partial.status_code}", json.dumps(partial.json(), indent=2))
if partial.status_code != 200:
    sys.exit(f"Expected HTTP 200, got {partial.status_code}")
partial=$(batch ROLLBACK_FAILED | gql)
echo "$partial"
if jq -e '.errors' <<<"$partial" >/dev/null; then
  echo "Unexpected GraphQL errors" >&2
  exit 1
fi
const partial = await gql(ADD_CONTACTS, { input: contacts, rollbackMethod: "ROLLBACK_FAILED" });
console.log("ROLLBACK_FAILED:", JSON.stringify(partial, null, 2));
if (partial.errors) throw new Error(`Unexpected GraphQL errors: ${JSON.stringify(partial.errors)}`);
partial = gql(ADD_CONTACTS, {"input": contacts, "rollbackMethod": "ROLLBACK_FAILED"})
print("ROLLBACK_FAILED:", json.dumps(partial, indent=2))
if partial.get("errors"):
    sys.exit(f"Unexpected GraphQL errors: {json.dumps(partial['errors'])}")
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "success": false,
  "data": [
    "0c000000-0000-4000-a000-000000000002",
    "0c000000-0000-4000-a000-000000000003",
    {
      "code": 40000,
      "name": "ERROR_ENTITY_VALIDATION",
      "message": "\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000004].owner' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000004].unit' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000004].owner_id' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000004].unit_id' is null or empty."
    }
  ]
}
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "data": {
    "bulkUpdateContact": {
      "result": {
        "created": [
          {
            "index": 0,
            "id": "0c000000-0000-4000-a000-000000000002"
          },
          {
            "index": 1,
            "id": "0c000000-0000-4000-a000-000000000003"
          }
        ],
        "errors": [
          {
            "index": 2,
            "code": 40000,
            "message": "\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000004].owner' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000004].unit' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000004].owner_id' is null or empty.\nNon-null field 'Contact'[0c000000-0000-4000-a000-000000000004].unit_id' is null or empty."
          }
        ]
      }
    }
  }
}
Show all 25 linesShow less

Read per-item results

data holds one entry per input item, in input order:

Item Entry in data Meaning
0 "0c000000-…-000000000002" Created; the record's ID
1 "0c000000-…-000000000003" Created
2 { "code": 40000, "name": "ERROR_ENTITY_VALIDATION", "message": "…" } Failed; nothing was written for this item

An error entry has only code, name and message, without field_errors. In GraphQL, rollbackMethod: ROLLBACK_FAILED returns result.created and result.errors, each entry with the index of its input item.

Checking only the HTTP status loses data

With rollback-method=1, a batch with failed items still answers HTTP 200. An integration that treats every 200 as success drops the failed items without noticing. Always walk data, and treat success: false as "some items failed".

rollback-method

Value GraphQL Effect
0 (default) ROLLBACK_ALL One invalid item fails the whole batch: HTTP 422 (404 for an unknown ID in batch-delete). Nothing is written, and the error does not say which item failed.
1 ROLLBACK_FAILED HTTP 200 with success: false when an item failed. The valid items are written; each failed item has its error at its position in data.

rollback-method applies to batch-modify, batch-delete and batch-restore. validation-level applies to batch-modify only: on batch-delete and batch-restore it is refused with HTTP 400, code 118 ERROR_PARAMETER_UNKNOWN. See validation.

Check every item in code

Walk data after every batch sent with rollback-method=1, and report or retry the failed items:

# One line per item, in input order: the new ID, or the error in its place.
jq -r '.data | to_entries[]
  | if (.value | type) == "string" then "\(.key) created \(.value)"
    else "\(.key) FAILED \(.value.code) \(.value.name)" end' <<<"$partial"
type ItemError = { code: number; name: string; message: string };
const results = partial.body.data.map((item: string | ItemError, index: number) =>
  typeof item === "string"
    ? { index, ok: true, id: item }
    : { index, ok: false, code: item.code, message: item.message },
);
const failed = results.filter((r: { ok: boolean }) => !r.ok);
if (failed.length) console.warn(`${failed.length} of ${results.length} items failed`, failed);
results = [
    {"index": index, "ok": True, "id": item}
    if isinstance(item, str)
    else {"index": index, "ok": False, "code": item["code"], "message": item["message"]}
    for index, item in enumerate(partial.json()["data"])
]
failed = [result for result in results if not result["ok"]]
if failed:
    print(f"{len(failed)} of {len(results)} items failed", failed, file=sys.stderr)
jq -r '.data.bulkUpdateContact.result
  | (.created[] | "\(.index) created \(.id)"),
    (.errors[] | "\(.index) FAILED \(.code)")' <<<"$partial"
const { created, errors } = partial.data.bulkUpdateContact.result;
for (const item of created) console.log(`${item.index} created ${item.id}`);
for (const item of errors) console.warn(`${item.index} FAILED ${item.code}: ${item.message.trim()}`);
result = partial["data"]["bulkUpdateContact"]["result"]
for item in result["created"]:
    print(f"{item['index']} created {item['id']}")
for item in result["errors"]:
    print(f"{item['index']} FAILED {item['code']}: {item['message'].strip()}", file=sys.stderr)

Limits

Limit Value Details
Batch size None set Up to 100 per request recommended; 500 take about 20 s
Which item failed Not reported by default Use rollback-method=1 to get per-item results
validation-level batch-modify only Refused on batch-delete and batch-restore with HTTP 400, code 118
Relation records Not deleted with their records Delete ContactAccountRelations and the CloudObject of a removed attachment on their own
Request body size None documented A batch of 500 records (42 kB) is accepted
Verified on 2026-10-15 against API 6.5Behaviour-verified

Every sample on this page ran against the docs sandbox and returned the response shown.