Get startedQuickstart: your first API call
Quickstart: your first API call
Create an API application in Coevera, copy its credentials and list your accounts, in REST or GraphQL, with cURL, TypeScript or Python.
Answer
An admin creates an API application in the CRM and copies its user name, password, space ID and service URL. Your code sends the user name and password with HTTP Basic auth to https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}. A GET on /entities/Accounts with first=3 returns the first three accounts as JSON.
From an empty terminal to a first successful call: create an API application, copy its credentials, and list the accounts in your space. It takes about five minutes, in REST or GraphQL.
Before you begin
- A Coevera space in which you hold the Admin API Access right. Only such a user can open Applications; for everyone else the Obtain API Key button is disabled.
- A space to test in. Coevera has no self-serve developer sandbox, so use a space without production data: a 14-day free trial gives you a space of your own. Every Coevera plan includes the API; the monthly call limits are in Plans and limits.
curl, Node.js 18 or later, or Python 3.9 or later withrequests.
Get API credentials
Every integration authenticates as an API application. Each one has its own user name, password and permissions, so you can give an integration only the access it needs and revoke it without breaking the others.
-
Open the applications list. In the CRM, open Administration from the space menu (the app switcher calls it Admin) and go to Units, Users & Roles › Applications. The button Administration › General › Obtain API Key opens the same place.
-
Create the application. Click Create New and name it after your integration, for example Northwind ERP sync. Then click Edit Permissions: Read Only Access on Accounts, in the Records tab, is enough for this quickstart.
-
Copy the access values. Click Show Access. The API Access dialog shows four values, each with a copy button:
Value Where it goes User Name COEVERA_. It starts with your space ID.API_ KEY Password COEVERA_. It is shown in full only right after the application is created or its credentials are regenerated, so copy it then.API_ SECRET Space ID The context bar at the top of this page, and every URL. Service URL The host to call, in your space's region. Take it from here or from the discovery endpoint; do not build it by hand.
Generate New, in the same dialog, replaces both the user name and the password, and the previous pair stops working at once.
Only API applications of type Basic have Show Access. An application that an AI connector created is of type OAuth and offers View Permissions only.
Set your context
Pick your region and enter your space ID in the context bar at the top of the page: every sample on this site then fills in your base URL. Keep the credentials out of your code and out of every URL; export them as environment variables:
export COEVERA_API_KEY="<application user name>"
export COEVERA_API_SECRET="<application password>" # never commit itThe REST API for a space answers at:
https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}The region is one of us-east, eu-central, ca-central or ap-southeast; see
Regions and base URLs.
Make your first request
List the first three accounts in the space, with only the fields you need: first=3 asks for
three records and load-only names the fields each one carries. Pick REST or GraphQL, and your
language:
COEVERA_BASE="https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}"
curl -sS --fail-with-body -G "$COEVERA_BASE/entities/Accounts" \
-d first=3 \
-d load-only=id,name,email1,modified \
-u "$COEVERA_API_KEY:$COEVERA_API_SECRET"import { Buffer } from "node:buffer";
import { env } from "node:process";
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");
const params = new URLSearchParams({ first: "3", "load-only": "id,name,email1,modified" });
const res = await fetch(`${base}/entities/Accounts?${params}`, {
headers: { Authorization: `Basic ${auth}` },
});
const body = await res.json();
if (!res.ok) throw new Error(`HTTP ${res.status} ${body.name}: ${body.message}`);
console.log(JSON.stringify(body, null, 2));import json
import os
import sys
import requests
BASE = "https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}"
AUTH = (os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])
response = requests.get(
f"{BASE}/entities/Accounts",
params={"first": 3, "load-only": "id,name,email1,modified"},
auth=AUTH,
timeout=30,
)
body = response.json()
if not response.ok:
sys.exit(f"HTTP {response.status_code} {body.get('name')}: {body.get('message')}")
print(json.dumps(body, indent=2))COEVERA_GRAPHQL="https://{region}.api.coevera.com/api/v100/app/space/{space_id}/graphql/public"
curl -sS --fail-with-body "$COEVERA_GRAPHQL" \
-u "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"query": "{ entities { account { getByCriteria(first: 3) { totalCount pageInfo { hasNextPage endCursor } edges { node { id name email1 modified } } } } } }"}'import { Buffer } from "node:buffer";
import { env } from "node:process";
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");
const res = await fetch(endpoint, {
method: "POST",
headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/json" },
body: JSON.stringify({
query: `{
entities {
account {
getByCriteria(first: 3) {
totalCount
pageInfo { hasNextPage endCursor }
edges { node { id name email1 modified } }
}
}
}
}`,
}),
});
const body = await res.json();
// GraphQL reports errors in the body, often with HTTP 200: check both.
if (!res.ok || body.errors) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body.errors ?? body)}`);
console.log(JSON.stringify(body.data, null, 2));import json
import os
import sys
import requests
ENDPOINT = (
"https://{region}.api.coevera.com/api/v100/app/space/"
"{space_id}/graphql/public"
)
AUTH = (os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])
QUERY = """
{
entities {
account {
getByCriteria(first: 3) {
totalCount
pageInfo { hasNextPage endCursor }
edges { node { id name email1 modified } }
}
}
}
}
"""
response = requests.post(ENDPOINT, json={"query": QUERY}, auth=AUTH, timeout=30)
body = response.json()
# GraphQL reports errors in the body, often with HTTP 200: check both.
if not response.ok or body.get("errors"):
sys.exit(f"HTTP {response.status_code}: {json.dumps(body.get('errors', body))}")
print(json.dumps(body["data"], indent=2))The response looks like this; the ids and names in your own space differ:
{
"success": true,
"total": 4,
"page_info": {
"start_cursor": "WyIwYTAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDEiXQ==",
"end_cursor": "WyIwYTAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDMiXQ==",
"has_previous_page": false,
"has_next_page": true
},
"data": [
{
"id": "0a000000-0000-4000-a000-000000000001",
"modified": "2026-10-14 09:00:00.000000+00:00",
"email1": "northwind@example.com",
"name": "Northwind Analytics"
},
{
"id": "0a000000-0000-4000-a000-000000000002",
"modified": "2026-10-14 09:00:00.740429+00:00",
"email1": "contoso@example.com",
"name": "Contoso Freight"
},
{
"id": "0a000000-0000-4000-a000-000000000003",
"modified": "2026-10-14 09:00:01.285049+00:00",
"email1": "wideworld@example.com",
"name": "Wide World Importers"
}
]
}Show all 30 linesShow less
{
"data": {
"entities": {
"account": {
"getByCriteria": {
"totalCount": 4,
"pageInfo": {
"hasNextPage": true,
"endCursor": "WyIwYTAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDMiXQ=="
},
"edges": [
{
"node": {
"id": "0a000000-0000-4000-a000-000000000001",
"name": "Northwind Analytics",
"email1": "northwind@example.com",
"modified": "2026-10-14T09:00:00.000000+00:00"
}
},
{
"node": {
"id": "0a000000-0000-4000-a000-000000000002",
"name": "Contoso Freight",
"email1": "contoso@example.com",
"modified": "2026-10-14T09:00:00.740429+00:00"
}
},
{
"node": {
"id": "0a000000-0000-4000-a000-000000000003",
"name": "Wide World Importers",
"email1": "wideworld@example.com",
"modified": "2026-10-14T09:00:01.285049+00:00"
}
}
]
}
}
}
}
}Show all 41 linesShow less
A REST list comes back in an envelope: success, total (how many accounts match, not how many
came back), page_info with the cursors for the next page, and data with the records. A
single record comes back as success and data. In GraphQL the list comes back as
totalCount, pageInfo and edges. Continue with pagination to read
more than one page, and querying to filter, sort and trim a list. Every
entity and its operations are in the REST reference and the
GraphQL reference.
Limit
A REST list request returns at most 100 records per page, and 30 when you do not set first.
A larger first answers 400 with ERROR_PARAMETER_INVALID. In GraphQL, set first on every
list query.
If you get 401
Every authentication failure answers HTTP 401 with ERROR_UNAUTHORIZED, and the reason is in
message; the other errors are in errors. This is what a request without
credentials gets; in GraphQL the status is the same and the body is shorter:
{
"code": 401,
"name": "ERROR_UNAUTHORIZED",
"message": "Access denied (Unauthorized call.).",
"http_status": 401,
"success": false
}{
"status": 401,
"message": "Access denied (Unauthorized call.).",
"errorcode": null,
"traceback": null
}The causes, each with its fix:
- No credentials. The reason reads
Unauthorized call.Check that both environment variables are set in the shell that runs the program. - Wrong space. The user name starts with the ID of the space it belongs to, and works only
there. If the space in the URL is a different one, the call is refused like a wrong password:
the reason reads
Invalid Api user credentials, and it counts as a failed sign-in. Use the space ID from Show Access. - Credentials regenerated. Someone pressed Generate New, which replaces the user name and the password. Copy both new values.
A wrong region is not a cause
A call to another region's host is forwarded to the region that holds your space and answers normally, with the same credentials, only more slowly. Use the region the discovery endpoint returns.
Next steps
- Create recordsAn account with its contacts, in one batch.
- Query dataFilters, sort, expand and the query builder.
- React to changesWebhooks with signed deliveries.
Before you build on it, read authentication for how the credentials are sent and rotated, permissions and scopes for what an application may do, and REST or GraphQL? for which API to use. Troubleshooting has the common failures in one place.