Part 1: How to Authenticate with the SuiteCRM v8 REST API Using OAuth2 (you are here) · Part 2: SuiteCRM v8 REST API CRUD Operations
This guide walks you through connecting to the SuiteCRM v8 REST API using OAuth2 authentication — from registering your client to sending your first authenticated request. Whether you are building a custom integration, a mobile app, or an automated workflow, secure token-based auth is the mandatory first step every developer must get right.
1. Why the SuiteCRM API Matters
SuiteCRM holds your leads, accounts, opportunities, and support cases. But if that data only lives inside the CRM UI, you cannot build the integrations your business actually needs. The SuiteCRM API is how external systems read and write CRM records programmatically — securely, predictably, and at scale.
Every integration project starts the same way: something outside SuiteCRM needs CRM data, or CRM needs data from somewhere else. A customer portal that shows open cases. A mobile app that lets field reps update opportunities. A WhatsApp bot that logs inbound messages as leads. An AI assistant that summarizes account history before a sales call. An ERP or accounting system that syncs invoices and orders. A website form that creates leads the moment someone submits.
Without an API, teams fall back on CSV exports, screen scraping, or brittle database queries. Those approaches break under load, bypass CRM security, and never trigger workflows properly. The SuiteCRM REST API gives you a supported path: OAuth2-authenticated HTTP requests that respect user permissions, fire CRM logic, and return structured data you can build on.
This is Part 1 of a two-part developer guide. Here we cover setup, authentication, and your first request. Part 2 covers CRUD operations, filtering, relationships, and production patterns. Every endpoint matches the official SuiteCRM API documentation.
2. Understanding the SuiteCRM v8 API
Before writing code, it helps to understand what you are talking to. The SuiteCRM v8 API is a REST API — you send HTTP requests (GET, POST, PATCH, DELETE) to specific URLs and receive structured responses. It follows the JSON:API specification, which means responses have a predictable shape: a data object (or array), optional meta, and optional links for pagination.
Resources and modules
Each CRM record is a resource. A Lead, Account, or Case is a resource with a type (module name), an id (UUID), and attributes (field values). Relationship links appear under relationships when you fetch a record. Module endpoints live under /Api/V8/module/{moduleName}.
Authentication
The API does not accept username/password on every request. Instead, you authenticate once via SuiteCRM OAuth2, receive a short-lived access_token, and send it as a Bearer token in the Authorization header. Tokens expire (typically after 3600 seconds). Password grants also return a refresh_token so you can obtain new access tokens without re-entering credentials.
Required headers
Every API call after authentication needs:
Accept: application/vnd.api+jsonContent-Type: application/vnd.api+json(for POST and PATCH)Authorization: Bearer {access_token}
3. Preparing Your SuiteCRM Instance
A working API starts with a properly configured CRM instance. Skip these steps and you will chase 401 and 500 errors that have nothing to do with your integration code.
Requirements
- HTTPS — OAuth tokens must not travel over plain HTTP in production.
- Composer packages — Run
composer installin your SuiteCRM root. - OAuth2 keys — Generate
private.keyandpublic.key(see API setup guide). Set permissions to 600. - OAuth2 encryption key — Stored in
config.phpasoauth2_encryption_key. - Apache mod_rewrite — Enabled with
AllowOverride All. - OAuth2 client — Created in Admin → OAuth2 Clients and Tokens.
Create an OAuth2 client
Go to Administration → OAuth2 Clients and Tokens and create a Password Client (for integrations acting on behalf of a CRM user) or a Client Credentials Client (for machine-to-machine jobs). Save the client_id and client_secret immediately — the secret is hashed on save and cannot be retrieved later.
Developer tools
- Swagger JSON:
{BASE_URL}/Api/docs/swagger/swagger.json - API meta:
GET {BASE_URL}/Api/V8/meta/swagger.json - Postman collection:
{BASE_URL}/Api/docs/postman/SalesAgility.postman_collection.json
SuiteCRM 8.x path note
Official docs use {BASE_URL}/Api/.... On many SuiteCRM 8.x installs, the working path includes /legacy/, for example {BASE_URL}/legacy/Api/access_token. Always confirm against your instance Swagger before hard-coding URLs.
4. Authentication — OAuth2 Step by Step
Why: The API requires an active OAuth2 session. You exchange credentials for a token once, then reuse that token until it expires.
When: At the start of every integration session, and again after token expiry.
What could go wrong: Wrong client_secret, expired token, or incorrect base URL (missing /legacy/ on SuiteCRM 8).
Password grant (recommended for user-context integrations)
POST to {BASE_URL}/Api/access_token with a JSON body:
{
"grant_type": "password",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"username": "admin",
"password": "your_password"
}
cURL
curl -X POST "{BASE_URL}/Api/access_token" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-d '{
"grant_type": "password",
"client_id": "'"${CLIENT_ID}"'",
"client_secret": "'"${CLIENT_SECRET}"'",
"username": "'"${CRM_USERNAME}"'",
"password": "'"${CRM_PASSWORD}"'"
}'
PHP
<?php
$ch = curl_init();
$header = [
'Content-type: application/vnd.api+json',
'Accept: application/vnd.api+json',
];
$postStr = json_encode([
'grant_type' => 'password',
'client_id' => getenv('SUITECRM_CLIENT_ID'),
'client_secret' => getenv('SUITECRM_CLIENT_SECRET'),
'username' => getenv('SUITECRM_USERNAME'),
'password' => getenv('SUITECRM_PASSWORD'),
]);
$url = getenv('SUITECRM_BASE_URL') . '/Api/access_token';
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_POSTFIELDS, $postStr);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
$output = curl_exec($ch);
curl_close($ch);
$tokenData = json_decode($output, true);
$accessToken = $tokenData['access_token'] ?? null;
$refreshToken = $tokenData['refresh_token'] ?? null;
JavaScript (fetch)
const response = await fetch(`${process.env.BASE_URL}/Api/access_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/vnd.api+json',
'Accept': 'application/vnd.api+json',
},
body: JSON.stringify({
grant_type: 'password',
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
username: process.env.CRM_USERNAME,
password: process.env.CRM_PASSWORD,
}),
});
const tokenData = await response.json();
const accessToken = tokenData.access_token;
const refreshToken = tokenData.refresh_token;
A successful response returns:
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGci...",
"refresh_token": "def50200d2fb757e4c01c333..."
}
Client credentials grant (machine-to-machine)
Use this when no human user is involved — scheduled sync jobs or background workers. POST the same endpoint with grant_type: "client_credentials". The response includes access_token but not refresh_token.
Refresh an expired token
{
"grant_type": "refresh_token",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"refresh_token": "YOUR_REFRESH_TOKEN"
}
5. Making Your First API Request
Why: Fetching a single Account confirms auth works and shows you the response shape.
When: Immediately after obtaining a token.
What could go wrong: Missing Bearer prefix, wrong Accept header, or requesting a record the authenticated user cannot see.
Endpoint: GET {BASE_URL}/Api/V8/module/Accounts/{id}?fields[Accounts]=name,account_type
cURL
curl -X GET "{BASE_URL}/Api/V8/module/Accounts/11a71596-83e7-624d-c792-5ab9006dd493?fields[Accounts]=name,account_type" \
-H "Accept: application/vnd.api+json" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
PHP
<?php
$accountId = '11a71596-83e7-624d-c792-5ab9006dd493';
$url = getenv('SUITECRM_BASE_URL')
. '/Api/V8/module/Accounts/' . $accountId
. '?fields[Accounts]=name,account_type';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/vnd.api+json',
'Authorization: Bearer ' . $accessToken,
]);
$output = curl_exec($ch);
curl_close($ch);
$account = json_decode($output, true);
JavaScript (fetch)
const accountId = '11a71596-83e7-624d-c792-5ab9006dd493';
const url = `${process.env.BASE_URL}/Api/V8/module/Accounts/${accountId}?fields[Accounts]=name,account_type`;
const response = await fetch(url, {
headers: {
'Accept': 'application/vnd.api+json',
'Authorization': `Bearer ${accessToken}`,
},
});
const account = await response.json();
Expected response structure:
{
"data": {
"type": "Account",
"id": "11a71596-83e7-624d-c792-5ab9006dd493",
"attributes": {
"name": "White Cross Co",
"account_type": "Customer"
},
"relationships": {
"AOS_Contracts": {
"links": {
"related": "/V8/module/Accounts/11a71596-83e7-624d-c792-5ab9006dd493/relationships/aos_contracts"
}
}
}
}
}
Common errors on first request
- 401 Unauthorized — Token missing, expired, or revoked.
- 404 Not Found — Wrong base URL path or record ID does not exist.
- 400 Bad Request — Malformed query parameters or invalid field names.
What's Next in Part 2
Once you can authenticate and fetch a record, you are ready to build real integrations. In Part 2: SuiteCRM v8 REST API CRUD Operations: Create, Read, Update & Delete Records, we cover:
- Creating, updating, and deleting records (CRUD)
- Filtering, sorting, and pagination
- Working with relationships
- Production best practices and common mistakes
Frequently Asked Questions
What is the SuiteCRM API?
A RESTful interface using OAuth2 authentication with endpoints under /Api/V8/ for CRM records, metadata, and relationships.
How do I authenticate?
Create an OAuth2 client in SuiteCRM Admin, POST to /Api/access_token, and use the returned access_token as a Bearer token.
Why does SuiteCRM 8 return 404 on API paths?
Many SuiteCRM 8.x installs require /legacy/ in the URL path. Confirm via Swagger at /Api/docs/swagger/swagger.json.
Continue to Part 2
Learn CRUD operations, filtering, relationships, and production integration patterns.
Part 2: SuiteCRM v8 REST API CRUD Operations