06
July

0 Comments | 0 Shares | Urdhva Tech

The Complete SuiteCRM v8 REST API Developer Series
Part 1: How to Authenticate with the SuiteCRM v8 REST API Using OAuth2  ·  Part 2: SuiteCRM v8 REST API CRUD Operations (you are here)

New here? Start with Part 1: How to Authenticate with the SuiteCRM v8 REST API Using OAuth2 to set up OAuth2 and make your first GET request. This article assumes you have a working access_token and understand the required headers.

Once authenticated, you can perform full CRUD operations against the SuiteCRM v8 REST APICreate, Read, Update, and Delete records across any CRM module. This article covers the endpoints, request shapes, and patterns you need to query lists, write data, and manage relationships in production.

1. CRUD Operations — Real Business Scenarios

Create a Lead from a website form

Why: Capture inbound interest instantly instead of batch-importing CSV files.
Endpoint: POST {BASE_URL}/Api/V8/module

{
  "data": {
    "type": "Leads",
    "attributes": {
      "first_name": "Jane",
      "last_name": "Prospect",
      "email1": "This email address is being protected from spambots. You need JavaScript enabled to view it.
 ",
      "lead_source": "Web Site"
    }
  }
}

cURL

curl -X POST "{BASE_URL}/Api/V8/module" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -d '{"data":{"type":"Leads","attributes":{"first_name":"Jane","last_name":"Prospect","email1":"This email address is being protected from spambots. You need JavaScript enabled to view it.
 ","lead_source":"Web Site"}}}'

PHP

<?php
$body = json_encode([
    'data' => [
        'type' => 'Leads',
        'attributes' => [
            'first_name'  => 'Jane',
            'last_name'   => 'Prospect',
            'email1'      => 'This email address is being protected from spambots. You need JavaScript enabled to view it.
 ',
            'lead_source' => 'Web Site',
        ],
    ],
]);
$ch = curl_init(getenv('SUITECRM_BASE_URL') . '/Api/V8/module');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => 'POST',
    CURLOPT_POSTFIELDS     => $body,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/vnd.api+json',
        'Accept: application/vnd.api+json',
        'Authorization: Bearer ' . $accessToken,
    ],
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);

JavaScript (fetch)

const response = await fetch(`${process.env.BASE_URL}/Api/V8/module`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/vnd.api+json',
    'Accept': 'application/vnd.api+json',
    'Authorization': `Bearer ${accessToken}`,
  },
  body: JSON.stringify({
    data: {
      type: 'Leads',
      attributes: {
        first_name: 'Jane',
        last_name: 'Prospect',
        email1: 'This email address is being protected from spambots. You need JavaScript enabled to view it.
 ',
        lead_source: 'Web Site',
      },
    },
  }),
});
const lead = await response.json();

Update an Opportunity after payment

Why: Close the loop when your payment gateway confirms a deal.
Endpoint: PATCH {BASE_URL}/Api/V8/module — note the ID goes in the body, not the URL.

Common mistake: Sending PATCH to /Api/V8/module/Opportunities/{id}. The official endpoint is PATCH /Api/V8/module with data.id in the body.

{
  "data": {
    "type": "Opportunities",
    "id": "YOUR_OPPORTUNITY_ID",
    "attributes": {
      "sales_stage": "Closed Won",
      "amount": "15000.00"
    }
  }
}

cURL

curl -X PATCH "{BASE_URL}/Api/V8/module" \
  -H "Content-Type: application/vnd.api+json" \
  -H "Accept: application/vnd.api+json" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -d '{"data":{"type":"Opportunities","id":"YOUR_OPPORTUNITY_ID","attributes":{"sales_stage":"Closed Won","amount":"15000.00"}}}'

Close a Case after customer support

{
  "data": {
    "type": "Cases",
    "id": "YOUR_CASE_ID",
    "attributes": {
      "status": "Closed",
      "resolution": "Issue resolved via API integration."
    }
  }
}

Send the same PATCH endpoint with the case type, ID, and updated status attributes.

Delete a test record

Endpoint: DELETE {BASE_URL}/Api/V8/module/{moduleName}/{id}

curl -X DELETE "{BASE_URL}/Api/V8/module/Leads/YOUR_LEAD_ID" \
  -H "Accept: application/vnd.api+json" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

PHP

<?php
$url = getenv('SUITECRM_BASE_URL') . '/Api/V8/module/Leads/' . $leadId;
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Accept: application/vnd.api+json',
        'Authorization: Bearer ' . $accessToken,
    ],
]);
curl_exec($ch);
curl_close($ch);

JavaScript (fetch)

await fetch(`${process.env.BASE_URL}/Api/V8/module/Leads/${leadId}`, {
  method: 'DELETE',
  headers: {
    'Accept': 'application/vnd.api+json',
    'Authorization': `Bearer ${accessToken}`,
  },
});

2. Filtering, Sorting, and Pagination

Listing every record in a module does not scale. Production integrations must filter, sort, and paginate.

Filter

Supported operators include eq, neq, gt, gte, lt, lte, combined with AND or OR.

{BASE_URL}/Api/V8/module/Accounts?filter[account_type][eq]=Customer

With explicit AND operator:

{BASE_URL}/Api/V8/module/Accounts?fields[Accounts]=name,account_type&filter[operator]=and&filter[account_type][eq]=Customer

Sort

Ascending by default. Prefix with - for descending. Single-field sort only.

{BASE_URL}/Api/V8/module/Accounts?sort=-name

Pagination

Why pagination matters: Fetching thousands of records in one call causes timeouts and incomplete syncs. Always paginate list endpoints in production.

{BASE_URL}/Api/V8/module/Accounts?fields[Account]=name,account_type&page[number]=3&page[size]=10

Response includes pagination metadata:

{
  "meta": { "total-pages": 54 },
  "data": [ ... ],
  "links": {
    "first": "/V8/module/Accounts?...&page[number]=1&page[size]=10",
    "prev":  "/V8/module/Accounts?...&page[number]=2&page[size]=10",
    "next":  "/V8/module/Accounts?...&page[number]=4&page[size]=10",
    "last":  "/V8/module/Accounts?...&page[number]=54&page[size]=10"
  }
}

JavaScript pagination loop

let page = 1;
let hasMore = true;
while (hasMore) {
  const res = await fetch(
    `${process.env.BASE_URL}/Api/V8/module/Accounts?page[number]=${page}&page[size]=50`,
    { headers: { 'Accept': 'application/vnd.api+json', 'Authorization': `Bearer ${accessToken}` } }
  );
  const json = await res.json();
  processRecords(json.data);
  hasMore = json.links?.next != null;
  page++;
}

3. Working with Relationships

Why: CRM value is in connected data — contacts on an account, cases on a contact.
Endpoint: GET {BASE_URL}/Api/V8/module/{moduleName}/{id}/relationships/{linkFieldName}

Discover the link field name

Do not guess relationship names. Fetch the parent record and inspect the relationships object:

"relationships": {
  "AOS_Contracts": {
    "links": {
      "related": "/V8/module/Accounts/11a71596-83e7-624d-c792-5ab9006dd493/relationships/aos_contracts"
    }
  }
}

Or inspect via: GET {BASE_URL}/Api/V8/meta/fields/Accounts

Fetch related records

GET {BASE_URL}/Api/V8/module/Accounts/129a096c-5983-1d59-5ddf-5d95ec91c144/relationships/members

cURL

curl -X GET "{BASE_URL}/Api/V8/module/Accounts/129a096c-5983-1d59-5ddf-5d95ec91c144/relationships/members" \
  -H "Accept: application/vnd.api+json" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

Create a relationship

POST {BASE_URL}/Api/V8/module/{moduleName}/{id}/relationships/{linkFieldName}

{
  "data": {
    "type": "Contacts",
    "id": "RELATED_CONTACT_ID"
  }
}

Delete a relationship

DELETE {BASE_URL}/Api/V8/module/Accounts/{accountId}/relationships/{linkFieldName}/{relatedId}

4. Production Best Practices

  • Security — Store credentials in environment variables or a secrets manager. Use HTTPS. Scope OAuth clients to minimum required role.
  • Token management — Cache access tokens until near expiry, then refresh. Revoke compromised tokens from Admin.
  • Performance — Request only needed fields via fields[]. Paginate all list calls.
  • Logging — Log URLs and status codes on failure. Redact tokens from logs.
  • Error handling — Retry 5xx with exponential backoff. Fail fast on 401 and 400.

Build a thin API client class that handles auth refresh, headers, and error parsing once — then reuse it everywhere.


5. Real Business Integrations

The same API primitives power diverse production systems:

WhatsApp integration

An inbound message triggers POST to create a Lead or Case, then PATCH updates the record as agents respond.

Voice AI receptionist

Speech-to-text captures caller intent. The service queries Accounts by phone filter and creates Cases when needed.

AI-powered lead qualification

A scoring service reads new Leads via filtered GET, enriches with PATCH, and creates Opportunities for qualified leads.

QuickBooks synchronization

Payment webhooks PATCH Opportunities to Closed Won. Scheduled jobs paginate Accounts against accounting records.

Custom customer portal

Portal users see their Account, Contacts, and Cases via scoped API calls with a dedicated OAuth2 service user.


6. Common Mistakes and Troubleshooting

SymptomLikely causeFix
404 on /Api/access_token SuiteCRM 8 path missing /legacy/ Try {BASE_URL}/legacy/Api/access_token; confirm via Swagger
401 on data requests Expired or missing Bearer token Refresh or re-authenticate (Part 1: Authentication)
PATCH returns 404 ID sent in URL instead of body Use PATCH /Api/V8/module with data.id in body
Relationship 404 Wrong linkFieldName Inspect relationships on parent record
Empty field data Field not in fields[] or ACL issue Add field to query; verify user role permissions
Custom field not writable Field name differs from label Check GET /Api/V8/meta/fields/{moduleName}

7. Conclusion

The SuiteCRM API is the foundation for every serious integration — portals, messaging channels, AI tools, ERP sync, and custom apps. Combined with Part 1: How to Authenticate with the SuiteCRM v8 REST API Using OAuth2, you now have the full toolkit: OAuth2 auth, CRUD, filters, pagination, and relationships.

Next steps:

  1. Import the Postman collection from your instance.
  2. Explore /Api/V8/meta/modules and /Api/V8/meta/fields/{module}.
  3. Build a reusable API client with token refresh.
  4. Ship one integration endpoint to production before expanding scope.

Frequently Asked Questions

How do I update a record?

PATCH /Api/V8/module with data.type, data.id, and data.attributes in the body.

How do I paginate list requests?

Use page[number] and page[size] on GET list endpoints. Follow links.next until exhausted.

How do I find relationship link names?

Inspect the relationships object on a GET record response, or use GET /Api/V8/meta/fields/{moduleName}.

Where is the full endpoint list?

{BASE_URL}/Api/docs/swagger/swagger.json or GET {BASE_URL}/Api/V8/meta/swagger.json.

Quick Summary

  • CRUD: POST create, PATCH update (ID in body), DELETE by module/id.
  • Lists: Filter, sort, paginate with query parameters.
  • Relationships: Discover link names from record responses.

Need Help Building a SuiteCRM Integration?

Our team builds production SuiteCRM API integrations — custom portals, ERP sync, messaging channels, and more.

SuiteCRM 8 Development Services

Download attachments:
Comments
  • No Comments Found.
Post your comment