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

RecipesCreate an account with contacts

Create an account with contacts

Create an account, add its contacts in one batch with per-item results, and read them back, in REST or GraphQL, in cURL, TypeScript or Python.

  • 10 min
  • REST and GraphQL
  • cURL · TypeScript · Python
  • Verified in the docs sandbox on 2026-10-15

Answer

Look up the owner by e-mail, create the account with name and owner_id, then add the contacts in one batch with account_relations, one primary relation each. Send rollback-method=1 (GraphQL ROLLBACK_FAILED) so one bad contact does not undo the others, check every item, and read the contacts back by account.

Create Northwind Analytics with two contacts linked to it, owned by the same sales rep. You end with one account, two contacts and their account relations, read back from the API.

Before you start

Permissions Full Access to Accounts and Contacts on the Records tab of the application's permissions. The owner lookup also reads users (Clients).
validation-level The default (0, full validation). Do not relax it for new records; see Validation.
Batch behaviour Step 3 is a batch sent with rollback-method=1 (GraphQL rollbackMethod: ROLLBACK_FAILED): it answers 200 even if a contact fails. The valid contacts are written, and a failed one comes back as an error object. The program checks every item.
Context Region and space ID come from the context bar. Credentials come from $COEVERA_API_KEY and $COEVERA_API_SECRET.

The programs need curl and jq, Node.js 18 or later, or Python 3.9 or later with requests. Authentication shows where the credentials come from, and Permissions and scopes where to set the permissions.

The operations this recipe calls, in the API reference: REST GET /entities/Clients, POST /entities/Accounts, POST /entities/Contacts/batch-modify, GET /entities/Contacts; GraphQL ClientRepository.getByCriteria, createAccount, bulkUpdateContact, ContactRepository.getByCriteria.

Set up a helper

Every step below uses this setup, so the steps show only what changes. Export the credentials first:

Shell
export COEVERA_API_KEY="<application user name>"
export COEVERA_API_SECRET="<application password>"   # never commit it
COEVERA_BASE="https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}"
const base = `https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}`;
const auth = Buffer.from(`${env.COEVERA_API_KEY}:${env.COEVERA_API_SECRET}`).toString("base64");

async function coevera(path: string, init: { method?: string; body?: unknown } = {}) {
  const res = await fetch(`${base}${path}`, {
    method: init.method ?? "GET",
    headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/json" },
    body: init.body === undefined ? undefined : JSON.stringify(init.body),
  });
  const body = await res.json();
  if (!res.ok) throw new Error(`HTTP ${res.status} ${body.name}: ${body.message}`);
  return body;
}
BASE = "https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}"
session = requests.Session()
session.auth = (os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])


def coevera(method, path, **kwargs):
    response = session.request(method, f"{BASE}{path}", timeout=30, **kwargs)
    body = response.json()
    if not response.ok:
        sys.exit(f"HTTP {response.status_code} {body.get('name')}: {body.get('message')}")
    return body
COEVERA_GRAPHQL="https://{region}.api.coevera.com/api/v100/app/space/{space_id}/graphql/public"

# gql: POST the GraphQL request read from stdin. GraphQL reports errors in the body, often
# with HTTP 200, so fail on those too.
gql() {
  local response
  response=$(curl -sS --fail-with-body "$COEVERA_GRAPHQL" \
    -u "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
    -H "Content-Type: application/json" \
    -d @-)
  if jq -e '.errors' <<<"$response" >/dev/null; then
    echo "$response" >&2
    return 1
  fi
  echo "$response"
}
const endpoint = `https://{region}.api.coevera.com/api/v100/app/space/{space_id}/graphql/public`;
const auth = Buffer.from(`${env.COEVERA_API_KEY}:${env.COEVERA_API_SECRET}`).toString("base64");

// gql: one GraphQL request. GraphQL reports errors in the body, often with HTTP 200.
async function gql(query: string, variables: object = {}) {
  const res = await fetch(endpoint, {
    method: "POST",
    headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/json" },
    body: JSON.stringify({ query, variables }),
  });
  const body = await res.json();
  if (!res.ok || body.errors) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body.errors ?? body)}`);
  return body;
}
ENDPOINT = (
    "https://{region}.api.coevera.com/api/v100/app/space/"
    "{space_id}/graphql/public"
)
session = requests.Session()
session.auth = (os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])


def gql(query, variables=None):
    """One GraphQL request. GraphQL reports errors in the body, often with HTTP 200."""
    response = session.post(ENDPOINT, json={"query": query, "variables": variables or {}}, timeout=30)
    body = response.json()
    if not response.ok or body.get("errors"):
        sys.exit(f"HTTP {response.status_code}: {json.dumps(body.get('errors', body))}")
    return body["data"]

1. Find the owner

Every account and contact needs an owner. Look the sales rep up by e-mail in Clients, the API name for users. The programs encode the address: a raw + in a query string arrives as a space.

owner=$(curl -sS --fail-with-body -G "$COEVERA_BASE/entities/Clients" \
  --data-urlencode "filter[email]=dana.whitfield@example.com" \
  -d load-only=id,name,default_unit_id \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET")
echo "$owner"
OWNER_ID=$(jq -er '.data[0].id' <<<"$owner")
// URLSearchParams encodes the address: a raw "+" in a query string arrives as a space.
const lookup = new URLSearchParams({
  "filter[email]": "dana.whitfield@example.com",
  "load-only": "id,name,default_unit_id",
});
const owners = await coevera(`/entities/Clients?${lookup}`);
const ownerId: string | undefined = owners.data[0]?.id;
if (!ownerId) throw new Error("No user with that e-mail in this space.");
console.log("Owner:", owners.data[0].name, ownerId);
owners = coevera(
    "GET",
    "/entities/Clients",
    params={"filter[email]": "dana.whitfield@example.com", "load-only": "id,name,default_unit_id"},
)
if not owners["data"]:
    sys.exit("No user with that e-mail in this space.")
owner_id = owners["data"][0]["id"]
print("Owner:", owners["data"][0]["name"], owner_id)
owner=$(jq -n '{
  query: "{ entities { client { getByCriteria(first: 1, filter: { email: { eq: [\"dana.whitfield@example.com\"] } }) { edges { node { id name } } } } } }"
}' | gql)
echo "$owner"
OWNER_ID=$(jq -er '.data.entities.client.getByCriteria.edges[0].node.id' <<<"$owner")
const { data: found } = await gql(`{
  entities {
    client {
      getByCriteria(first: 1, filter: { email: { eq: ["dana.whitfield@example.com"] } }) {
        edges { node { id name } }
      }
    }
  }
}`);
const ownerId: string | undefined = found.entities.client.getByCriteria.edges[0]?.node.id;
if (!ownerId) throw new Error("No user with that e-mail in this space.");
console.log("Owner:", found.entities.client.getByCriteria.edges[0].node.name, ownerId);
found = gql("""
{
  entities {
    client {
      getByCriteria(first: 1, filter: { email: { eq: ["dana.whitfield@example.com"] } }) {
        edges { node { id name } }
      }
    }
  }
}
""")
edges = found["entities"]["client"]["getByCriteria"]["edges"]
if not edges:
    sys.exit("No user with that e-mail in this space.")
owner_id = edges[0]["node"]["id"]
print("Owner:", edges[0]["node"]["name"], owner_id)
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "success": true,
  "total": 1,
  "page_info": {
    "start_cursor": "WyIwZDAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDEiXQ==",
    "end_cursor": "WyIwZDAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDEiXQ==",
    "has_previous_page": false,
    "has_next_page": false
  },
  "data": [
    {
      "id": "0d000000-0000-4000-a000-000000000001",
      "default_unit_id": "05000000-0000-4000-a000-000000000001",
      "name": "Dana Whitfield"
    }
  ]
}
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "data": {
    "entities": {
      "client": {
        "getByCriteria": {
          "edges": [
            {
              "node": {
                "id": "0d000000-0000-4000-a000-000000000001",
                "name": "Dana Whitfield"
              }
            }
          ]
        }
      }
    }
  }
}

2. Create the account

name and owner_id are required: without either, the API answers 422. The sales unit (unit_id) defaults to the owner's default unit.

account=$(curl -sS --fail-with-body "$COEVERA_BASE/entities/Accounts" \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
  -H "Content-Type: application/json" \
  -d @- <<JSON
{
  "name": "Northwind Analytics",
  "owner_id": "$OWNER_ID",
  "email1": "northwind@example.com",
  "phone1": "+1 206 555 0100",
  "home_page": "https://northwind.example.com",
  "city": "Seattle",
  "country": "United States"
}
JSON
)
echo "$account"
ACCOUNT_ID=$(jq -er '.data.id' <<<"$account")
const account = await coevera("/entities/Accounts", {
  method: "POST",
  body: {
    name: "Northwind Analytics",
    owner_id: ownerId,
    email1: "northwind@example.com",
    phone1: "+1 206 555 0100",
    home_page: "https://northwind.example.com",
    city: "Seattle",
    country: "United States",
  },
});
const accountId: string = account.data.id;
console.log("Account:", accountId);
account = coevera(
    "POST",
    "/entities/Accounts",
    json={
        "name": "Northwind Analytics",
        "owner_id": owner_id,
        "email1": "northwind@example.com",
        "phone1": "+1 206 555 0100",
        "home_page": "https://northwind.example.com",
        "city": "Seattle",
        "country": "United States",
    },
)
account_id = account["data"]["id"]
print("Account:", account_id)
account=$(jq -n --arg owner "$OWNER_ID" '{
  query: "mutation CreateAccount($input: CreateAccountInput!) { createAccount(input: $input) { account { id name owner { id name } } } }",
  variables: {
    input: {
      name: "Northwind Analytics",
      ownerId: $owner,
      email1: "northwind@example.com",
      phone1: "+1 206 555 0100",
      homePage: "https://northwind.example.com",
      city: "Seattle",
      country: "United States"
    }
  }
}' | gql)
echo "$account"
ACCOUNT_ID=$(jq -er '.data.createAccount.account.id' <<<"$account")
const { data: created } = await gql(
  `mutation CreateAccount($input: CreateAccountInput!) {
    createAccount(input: $input) {
      account { id name owner { id name } }
    }
  }`,
  {
    input: {
      name: "Northwind Analytics",
      ownerId,
      email1: "northwind@example.com",
      phone1: "+1 206 555 0100",
      homePage: "https://northwind.example.com",
      city: "Seattle",
      country: "United States",
    },
  },
);
const accountId: string = created.createAccount.account.id;
console.log("Account:", accountId);
created = gql(
    """
    mutation CreateAccount($input: CreateAccountInput!) {
      createAccount(input: $input) {
        account { id name owner { id name } }
      }
    }
    """,
    {
        "input": {
            "name": "Northwind Analytics",
            "ownerId": owner_id,
            "email1": "northwind@example.com",
            "phone1": "+1 206 555 0100",
            "homePage": "https://northwind.example.com",
            "city": "Seattle",
            "country": "United States",
        }
    },
)
account_id = created["createAccount"]["account"]["id"]
print("Account:", account_id)
201 CreatedRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "success": true,
  "data": {
    "is_delete_protected": false,
    "id": "0a000000-0000-4000-a000-000000000001",
    "is_deleted": false,
    "modified": "2026-10-15 09:00:00.000000+00:00",
    "created": "2026-10-15 09:00:00.000040+00:00",
    "account_type": "https://eu-central.api.coevera.com/api/v100/rest/spaces/nv2_northwind_docs/entities/AccountTypes/03000000-0000-4000-a000-000000000001",
    "currency_exchange_rates_list": null,
    "customer_type": null,
    "industry": null,
    "owner": "https://eu-central.api.coevera.com/api/v100/rest/spaces/nv2_northwind_docs/entities/Clients/0d000000-0000-4000-a000-000000000001",
    "parent_account": null,
    "parent_account_relation_type": null,
    "picture": null,
    "unit": "https://eu-central.api.coevera.com/api/v100/rest/spaces/nv2_northwind_docs/entities/SalesUnits/05000000-0000-4000-a000-000000000001",
    "account_class": 1,
    "account_type_id": "03000000-0000-4000-a000-000000000001",
    "address": "",
    "city": "Seattle",
    "comments": "",
    "country": "United States",
    "currency_exchange_rates_list_id": null,
    "customer_type_id": null,
    "email1": "northwind@example.com",
    "email2": "",
    "email3": "",
    "email4": "",
    "email5": "",
    "health_category": null,
    "health_status": null,
    "home_page": "https://northwind.example.com",
    "industry_id": null,
    "is_archived": false,
    "last_ai_account_hierarchy_applied": null,
    "last_ai_org_chart_applied": null,
    "name": "Northwind Analytics",
    "owner_id": "0d000000-0000-4000-a000-000000000001",
    "parent_account_id": null,
    "parent_account_relation_type_id": null,
    "phone1": "+1 206 555 0100",
    "phone2": "",
    "phone3": "",
    "phone4": "",
    "phone5": "",
    "picture_id": null,
    "quick_parent_account_name": "",
    "share_mode": 0,
    "state_province": "",
    "unit_id": "05000000-0000-4000-a000-000000000001",
    "zip_code": "",
    "revision": 411,
    "formatted_name": "Northwind Analytics",
    "social_media": null,
    "sharing_units": [],
    "sharing_clients": [],
    "documents": [],
    "is_favorite": false,
    "is_unsubscribed": false,
    "tags": [],
    "static_profiles": [],
    "health": null,
    "modified_by_user": null
  }
}
Show all 66 linesShow less
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "data": {
    "createAccount": {
      "account": {
        "id": "0a000000-0000-4000-a000-000000000001",
        "name": "Northwind Analytics",
        "owner": {
          "id": "0d000000-0000-4000-a000-000000000001",
          "name": "Dana Whitfield"
        }
      }
    }
  }
}

3. Add the contacts in one batch

Each contact links to the account through account_relations, with its job title in position. Exactly one relation per contact must have is_primary: true: with none, or with two, the contact fails validation (field error code 117). A contact also needs first_name or last_name.

contacts=$(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 @- <<JSON
[
  {
    "first_name": "Ava", "last_name": "Lindqvist",
    "email1": "ava.lindqvist@example.com",
    "owner_id": "$OWNER_ID",
    "account_relations": [
      { "account_id": "$ACCOUNT_ID", "position": "Head of Data", "is_primary": true }
    ]
  },
  {
    "first_name": "Marco", "last_name": "Ruiz",
    "email1": "marco.ruiz@example.com",
    "owner_id": "$OWNER_ID",
    "account_relations": [
      { "account_id": "$ACCOUNT_ID", "position": "CFO", "is_primary": true }
    ]
  }
]
JSON
)
echo "$contacts"
# With rollback-method=1 a failed contact comes back as an error object in data: check every item.
failed=$(jq '[.data[] | objects] | length' <<<"$contacts")
if [ "$failed" -ne 0 ]; then
  echo "$failed contact(s) failed" >&2
  exit 1
fi
const batch = await coevera("/entities/Contacts/batch-modify?rollback-method=1", {
  method: "POST",
  body: [
    {
      first_name: "Ava",
      last_name: "Lindqvist",
      email1: "ava.lindqvist@example.com",
      owner_id: ownerId,
      account_relations: [{ account_id: accountId, position: "Head of Data", is_primary: true }],
    },
    {
      first_name: "Marco",
      last_name: "Ruiz",
      email1: "marco.ruiz@example.com",
      owner_id: ownerId,
      account_relations: [{ account_id: accountId, position: "CFO", is_primary: true }],
    },
  ],
});
// With rollback-method=1 a failed contact comes back as an error object in data: check every item.
const failed = batch.data.filter((item: unknown) => typeof item !== "string");
if (failed.length) throw new Error(`${failed.length} contact(s) failed: ${JSON.stringify(failed)}`);
console.log("Contacts:", batch.data);
batch = coevera(
    "POST",
    "/entities/Contacts/batch-modify",
    params={"rollback-method": 1},
    json=[
        {
            "first_name": "Ava",
            "last_name": "Lindqvist",
            "email1": "ava.lindqvist@example.com",
            "owner_id": owner_id,
            "account_relations": [
                {"account_id": account_id, "position": "Head of Data", "is_primary": True}
            ],
        },
        {
            "first_name": "Marco",
            "last_name": "Ruiz",
            "email1": "marco.ruiz@example.com",
            "owner_id": owner_id,
            "account_relations": [{"account_id": account_id, "position": "CFO", "is_primary": True}],
        },
    ],
)
# With rollback-method=1 a failed contact comes back as an error object in data: check every item.
failed = [item for item in batch["data"] if not isinstance(item, str)]
if failed:
    sys.exit(f"{len(failed)} contact(s) failed: {json.dumps(failed)}")
print("Contacts:", batch["data"])
contacts=$(jq -n --arg owner "$OWNER_ID" --arg account "$ACCOUNT_ID" '{
  query: "mutation AddContacts($input: [CreateOrUpdateContactInput!]!) { bulkUpdateContact(input: $input, rollbackMethod: ROLLBACK_FAILED) { result { created { index id } errors { index code message } } } }",
  variables: {
    input: [
      {
        firstName: "Ava", lastName: "Lindqvist",
        email1: "ava.lindqvist@example.com",
        ownerId: $owner,
        accountRelations: [{ accountId: $account, position: "Head of Data", isPrimary: true }]
      },
      {
        firstName: "Marco", lastName: "Ruiz",
        email1: "marco.ruiz@example.com",
        ownerId: $owner,
        accountRelations: [{ accountId: $account, position: "CFO", isPrimary: true }]
      }
    ]
  }
}' | gql)
echo "$contacts"
failed=$(jq '.data.bulkUpdateContact.result.errors | length' <<<"$contacts")
if [ "$failed" -ne 0 ]; then
  echo "$failed contact(s) failed" >&2
  exit 1
fi
const { data: added } = await gql(
  `mutation AddContacts($input: [CreateOrUpdateContactInput!]!) {
    bulkUpdateContact(input: $input, rollbackMethod: ROLLBACK_FAILED) {
      result {
        created { index id }
        errors { index code message }
      }
    }
  }`,
  {
    input: [
      {
        firstName: "Ava",
        lastName: "Lindqvist",
        email1: "ava.lindqvist@example.com",
        ownerId,
        accountRelations: [{ accountId, position: "Head of Data", isPrimary: true }],
      },
      {
        firstName: "Marco",
        lastName: "Ruiz",
        email1: "marco.ruiz@example.com",
        ownerId,
        accountRelations: [{ accountId, position: "CFO", isPrimary: true }],
      },
    ],
  },
);
const { created: contactIds, errors } = added.bulkUpdateContact.result;
if (errors.length) throw new Error(`${errors.length} contact(s) failed: ${JSON.stringify(errors)}`);
console.log("Contacts:", contactIds);
added = gql(
    """
    mutation AddContacts($input: [CreateOrUpdateContactInput!]!) {
      bulkUpdateContact(input: $input, rollbackMethod: ROLLBACK_FAILED) {
        result {
          created { index id }
          errors { index code message }
        }
      }
    }
    """,
    {
        "input": [
            {
                "firstName": "Ava",
                "lastName": "Lindqvist",
                "email1": "ava.lindqvist@example.com",
                "ownerId": owner_id,
                "accountRelations": [
                    {"accountId": account_id, "position": "Head of Data", "isPrimary": True}
                ],
            },
            {
                "firstName": "Marco",
                "lastName": "Ruiz",
                "email1": "marco.ruiz@example.com",
                "ownerId": owner_id,
                "accountRelations": [{"accountId": account_id, "position": "CFO", "isPrimary": True}],
            },
        ]
    },
)
result = added["bulkUpdateContact"]["result"]
if result["errors"]:
    sys.exit(f"{len(result['errors'])} contact(s) failed: {json.dumps(result['errors'])}")
print("Contacts:", result["created"])
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "success": true,
  "data": [
    "0c000000-0000-4000-a000-000000000001",
    "0c000000-0000-4000-a000-000000000002"
  ]
}
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "data": {
    "bulkUpdateContact": {
      "result": {
        "created": [
          {
            "index": 0,
            "id": "0c000000-0000-4000-a000-000000000001"
          },
          {
            "index": 1,
            "id": "0c000000-0000-4000-a000-000000000002"
          }
        ],
        "errors": []
      }
    }
  }
}

Warning

Without rollback-method=1, one failed contact fails the whole batch and nothing is written: REST answers 422. In GraphQL, a bulkUpdateContact without rollbackMethod uses ROLLBACK_ALL and answers HTTP 200 with errors and no data. Send ROLLBACK_FAILED to get per-item results. Batch operations shows both modes.

4. Read them back

Filter the contacts by the account they belong to, and check them here rather than in the batch response. REST filters on account_relations.account_id; GraphQL on accountRelations: { accountId: … }.

curl -sS --fail-with-body -G "$COEVERA_BASE/entities/Contacts" \
  --data-urlencode "filter[account_relations.account_id]=$ACCOUNT_ID" \
  -d load-only=id,first_name,last_name,email1 \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET"
const contacts = await coevera(
  `/entities/Contacts?filter[account_relations.account_id]=${accountId}&load-only=id,first_name,last_name,email1`,
);
console.log(contacts.total, "contacts on the account:", JSON.stringify(contacts.data, null, 2));
contacts = coevera(
    "GET",
    "/entities/Contacts",
    params={
        "filter[account_relations.account_id]": account_id,
        "load-only": "id,first_name,last_name,email1",
    },
)
print(contacts.get("total"), "contacts on the account:", json.dumps(contacts["data"], indent=2))
jq -n --arg account "$ACCOUNT_ID" '{
  query: "query ReadBack($account: String!) { entities { contact { getByCriteria(filter: { accountRelations: { accountId: { eq: [$account] } } }) { totalCount edges { node { id firstName lastName email1 } } } } } }",
  variables: { account: $account }
}' | gql
const { data: readBack } = await gql(
  `query ReadBack($accountId: String!) {
    entities {
      contact {
        getByCriteria(filter: { accountRelations: { accountId: { eq: [$accountId] } } }) {
          totalCount
          edges { node { id firstName lastName email1 } }
        }
      }
    }
  }`,
  { accountId },
);
const contacts = readBack.entities.contact.getByCriteria;
console.log(contacts.totalCount, "contacts on the account:", JSON.stringify(contacts.edges, null, 2));
read_back = gql(
    """
    query ReadBack($accountId: String!) {
      entities {
        contact {
          getByCriteria(filter: { accountRelations: { accountId: { eq: [$accountId] } } }) {
            totalCount
            edges { node { id firstName lastName email1 } }
          }
        }
      }
    }
    """,
    {"accountId": account_id},
)
contacts = read_back["entities"]["contact"]["getByCriteria"]
print(contacts["totalCount"], "contacts on the account:", json.dumps(contacts["edges"], indent=2))
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "success": true,
  "total": 2,
  "page_info": {
    "start_cursor": "WyIwYzAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDEiXQ==",
    "end_cursor": "WyIwYzAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDIiXQ==",
    "has_previous_page": false,
    "has_next_page": false
  },
  "data": [
    {
      "id": "0c000000-0000-4000-a000-000000000001",
      "email1": "ava.lindqvist@example.com",
      "first_name": "Ava",
      "last_name": "Lindqvist"
    },
    {
      "id": "0c000000-0000-4000-a000-000000000002",
      "email1": "marco.ruiz@example.com",
      "first_name": "Marco",
      "last_name": "Ruiz"
    }
  ]
}
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "data": {
    "entities": {
      "contact": {
        "getByCriteria": {
          "totalCount": 2,
          "edges": [
            {
              "node": {
                "id": "0c000000-0000-4000-a000-000000000001",
                "firstName": "Ava",
                "lastName": "Lindqvist",
                "email1": "ava.lindqvist@example.com"
              }
            },
            {
              "node": {
                "id": "0c000000-0000-4000-a000-000000000002",
                "firstName": "Marco",
                "lastName": "Ruiz",
                "email1": "marco.ruiz@example.com"
              }
            }
          ]
        }
      }
    }
  }
}
Show all 29 linesShow less

What to check

  • Step 1 returns exactly one user. An empty data (GraphQL edges) means the e-mail is not a user of the space.
  • Step 2 answers 201 (GraphQL: 200), and data.id (GraphQL createAccount.account.id) is the account ID the next steps use.
  • Step 3 answers 200 with "success": true, and data holds two IDs, in the order you sent the contacts. With rollback-method=1, a failed contact makes success false and leaves an object with code, name and message in its place; the other contact is still written. In GraphQL, result.created lists the written items and result.errors the failed ones, each by index.
  • Step 4 returns total: 2 (GraphQL totalCount: 2). If it returns 0, the relation is missing: check the account_id you sent.
  • Run the program twice and you get two accounts named Northwind Analytics and four contacts: the API does not refuse duplicates. To make it safe to repeat, look the account up by name first and reuse it.

Clean up

The program leaves the account and its two contacts in your space. To remove them, send their IDs to POST /entities/Contacts/batch-delete and POST /entities/Accounts/batch-delete, then delete their rows in ContactAccountRelations the same way.

Note

Deleting a contact or an account leaves its ContactAccountRelations rows active, still pointing at the deleted records. Find them with filter[account_id] and delete them too. Deletes are soft: deleted records stay readable with include-deleted=true.

Limits

  • The program stops at the first error and does not retry.
  • The API sets no batch size limit; a batch of 500 items is accepted.
  • It creates one account. For many accounts, create them in one Accounts/batch-modify call first, then send their contacts in a second batch.
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.