> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# SugarCRM

> Client-mode REST API connector for SugarCRM (Sugar Sell, Serve and Enterprise REST API v11_24): open Cases from machine alarms, add notes, close them, upsert records by an external id, keep Accounts current, and filter, count and search any module

Technical Preview

## Overview

A client-mode connector for the [SugarCRM REST API](https://support.sugarcrm.com/documentation/sugar_developer/sugar_developer_guide_25.2/integration/web_services/rest_api/) (Sugar Sell, Sugar Serve and Sugar Enterprise, SugarCloud or on-premise) that connects plant and product events to CRM records. A connected machine that raises an alarm can open a Case on the customer's Account, the team can add notes while it is handled, and the Case can be closed when the alarm clears. Records can be upserted by an external id kept in the `sync_key` field (an asset serial, for example), Accounts can be kept current from live data, and any module can be listed, filtered, counted and searched.

Most events take the module name from the payload (`Cases`, `Accounts`, `Contacts`, `Bugs`, custom modules), so one connector covers the whole instance. Topics use `crm/sugarcrm/<object>/<verb>`; each response is published on `<source topic>/response`.

> [!NOTE]
>
> The template was checked against the Sugar 25.2 REST API endpoint reference (each packaged endpoint is still published in the 26.1 reference) and a local mock of a Sugar instance. Sugar is hosted per customer, so there is no shared public host to probe. It has **not** been run against a real Sugar instance yet, so status stays blocked until an authenticated smoke passes.

## Setup

1. Set `BASE_ADDRESS` in the connector to your instance URL, for example `https://acme.sugarondemand.com`. Endpoints carry `/rest/v11_24`, the version the 25.2 reference covers. Newer releases keep serving older versions (26.1 goes up to `v11_27`), so change the prefix only if you need a newer endpoint.
2. Create a dedicated integration user. The `sync_key` events (`RecordGetBySyncKey`, `RecordUpsertBySyncKey`) need an admin user.
3. In **Admin > Configure API Platforms**, add a platform name for the broker (for example `coreflux`). Logging in with the default `base` platform can sign the same user out of the web client.

## Authentication

Sugar uses two-legged OAuth 2.0 with a password grant (see [REST API authentication](https://support.sugarcrm.com/documentation/sugar_developer/sugar_developer_guide_25.2/integration/web_services/rest_api/endpoints/oauth2token_post/)):

1. Publish to `crm/sugarcrm/oauth/token` (event `TokenGet`):

   ```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
   {"body": {"grant_type": "password", "client_id": "sugar", "client_secret": "", "username": "<user>", "password": "<password>", "platform": "coreflux"}}
   ```

2. Read `access_token` (valid 3600 seconds) and `refresh_token` (valid 14 days) from `crm/sugarcrm/oauth/token/response`. Pass `access_token` in every other payload; the connector sends it in the `OAuth-Token` header.

3. When a call answers 401, publish `{"body": {"grant_type": "refresh_token", "refresh_token": "<refresh token>", "client_id": "sugar", "client_secret": "", "platform": "coreflux"}}` to `crm/sugarcrm/oauth/refresh` for a new pair. Keep only the refresh token, never the password, in long-term storage.

> [!WARNING]
>
> Store the integration user's credentials as broker secrets (for example `SUGARCRM_USERNAME` and `SUGARCRM_PASSWORD`) and have a LoT action add them with `GET SECRET`. The token response topics carry live tokens: restrict them with a rule, never retain them, and do not put tokens in retained messages.

## Payload fields

| Field | Used by | Notes |
| - | - | - |
| `body` | `TokenGet`, `TokenRefresh` and every POST / PUT / PATCH event | JSON sent as the request body (record fields, a filter, or a bulk request list) |
| `access_token` | every event except `TokenGet` and `TokenRefresh` | Sent as the `OAuth-Token` header |
| `module` | the generic `Records...`, `Related...`, `DuplicateCheck`, `RecordAudit`, `EnumValues` events | Module name, case-sensitive: `Cases`, `Accounts`, `Contacts`, ... |
| `id` | events with `{id}` in the path | Record id (UUID) |
| `link` / `remote_id` | `RelatedList`, `RelatedCreate`, `RelationshipAdd` | Link field name (for example `cases`, `notes`, `contacts`) and the related record id |
| `field` | `EnumValues` | Dropdown field name, for example `status` |
| `sync_key` | `RecordGetBySyncKey`, `RecordUpsertBySyncKey` | External id stored in the record's `sync_key` field |
| `query` | `RecordsList`, `RelatedList`, `GlobalSearch`, `AccountsFilter` | URL-encoded query string |

## Events

| Event | Method | Endpoint | Source topic | Write |
| - | - | - | - | - |
| `TokenGet` | `POST` | `/rest/v11_24/oauth2/token` | `crm/sugarcrm/oauth/token` | |
| `TokenRefresh` | `POST` | `/rest/v11_24/oauth2/token` | `crm/sugarcrm/oauth/refresh` | |
| `Logout` | `POST` | `/rest/v11_24/oauth2/logout` | `crm/sugarcrm/oauth/logout` | |
| `Ping` | `GET` | `/rest/v11_24/ping` | `crm/sugarcrm/ping` | |
| `ServerTime` | `GET` | `/rest/v11_24/ping/whattimeisit` | `crm/sugarcrm/ping/time` | |
| `CurrentUser` | `GET` | `/rest/v11_24/me` | `crm/sugarcrm/me/get` | |
| `RecordsList` | `GET` | `/rest/v11_24/{module}?{query}` | `crm/sugarcrm/records/list` | |
| `RecordsFilter` | `POST` | `/rest/v11_24/{module}/filter` | `crm/sugarcrm/records/filter` | |
| `RecordsCount` | `POST` | `/rest/v11_24/{module}/filter/count` | `crm/sugarcrm/records/count` | |
| `RecordGet` | `GET` | `/rest/v11_24/{module}/{id}` | `crm/sugarcrm/records/get` | |
| `RecordCreate` | `POST` | `/rest/v11_24/{module}` | `crm/sugarcrm/records/create` | yes |
| `RecordUpdate` | `PUT` | `/rest/v11_24/{module}/{id}` | `crm/sugarcrm/records/update` | yes |
| `RecordGetBySyncKey` | `GET` | `/rest/v11_24/{module}/sync_key/{sync_key}` | `crm/sugarcrm/records/sync-key/get` | |
| `RecordUpsertBySyncKey` | `PATCH` | `/rest/v11_24/{module}/sync_key/{sync_key}` | `crm/sugarcrm/records/sync-key/upsert` | yes |
| `RelatedList` | `GET` | `/rest/v11_24/{module}/{id}/link/{link}?{query}` | `crm/sugarcrm/related/list` | |
| `RelatedCreate` | `POST` | `/rest/v11_24/{module}/{id}/link/{link}` | `crm/sugarcrm/related/create` | yes |
| `RelationshipAdd` | `POST` | `/rest/v11_24/{module}/{id}/link/{link}/{remote_id}` | `crm/sugarcrm/related/link` | yes |
| `DuplicateCheck` | `POST` | `/rest/v11_24/{module}/duplicateCheck` | `crm/sugarcrm/records/duplicate-check` | |
| `RecordAudit` | `GET` | `/rest/v11_24/{module}/{id}/audit` | `crm/sugarcrm/records/audit` | |
| `EnumValues` | `GET` | `/rest/v11_24/{module}/enum/{field}` | `crm/sugarcrm/enum/get` | |
| `GlobalSearch` | `GET` | `/rest/v11_24/globalsearch?{query}` | `crm/sugarcrm/search/global` | |
| `Bulk` | `POST` | `/rest/v11_24/bulk` | `crm/sugarcrm/bulk` | yes |
| `CaseCreate` | `POST` | `/rest/v11_24/Cases` | `crm/sugarcrm/cases/create` | yes |
| `CasesFilter` | `POST` | `/rest/v11_24/Cases/filter` | `crm/sugarcrm/cases/filter` | |
| `CaseNoteCreate` | `POST` | `/rest/v11_24/Cases/{id}/link/notes` | `crm/sugarcrm/cases/notes/create` | yes |
| `AccountsFilter` | `GET` | `/rest/v11_24/Accounts/filter?{query}` | `crm/sugarcrm/accounts/filter` | |
| `AccountUpdate` | `PUT` | `/rest/v11_24/Accounts/{id}` | `crm/sugarcrm/accounts/update` | yes |

No DELETE events are packaged on purpose. `TokenGet`, `TokenRefresh` and `Logout` are POSTs but only handle the session; `RecordsFilter`, `RecordsCount`, `CasesFilter` and `DuplicateCheck` are POSTs that only read.

## Connector excerpt

The full connector is in `SugarcrmRestApi.lot`. The first events look like this:

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE SugarCrmRestApi WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://your-instance.sugarondemand.com"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '60'
        WITH DEFAULT_CONTENT_TYPE "application/json"
    ADD EVENT TokenGet
        WITH SOURCE_TOPIC "crm/sugarcrm/oauth/token"
        WITH DESTINATION_TOPIC "crm/sugarcrm/oauth/token/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/rest/v11_24/oauth2/token"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Cache-Control" "no-cache"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    ADD EVENT RecordUpdate
        WITH SOURCE_TOPIC "crm/sugarcrm/records/update"
        WITH DESTINATION_TOPIC "crm/sugarcrm/records/update/response"
        WITH METHOD "PUT"
        WITH ENDPOINT "/rest/v11_24/{value.json.module}/{value.json.id}"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Cache-Control" "no-cache"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "OAuth-Token" "{value.json.access_token}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
    ADD EVENT CaseCreate
        WITH SOURCE_TOPIC "crm/sugarcrm/cases/create"
        WITH DESTINATION_TOPIC "crm/sugarcrm/cases/create/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/rest/v11_24/Cases"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Cache-Control" "no-cache"
            ADD "User-Agent" "Coreflux-Broker REST_API route (https://www.coreflux.org)"
            ADD "OAuth-Token" "{value.json.access_token}"
            ADD "Content-Type" "application/json"
        WITH BODY "{value.json.body}"
```

## Examples

**Case from an alarm**

Find the customer's Account with `crm/sugarcrm/accounts/filter` (`"query": "filter%5B0%5D%5Bname%5D%5B%24starts%5D=ACME&fields=id,name"`), then publish to `crm/sugarcrm/cases/create`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<from TokenGet>", "body": {"name": "Press 7 overtemperature (E42)", "account_id": "<account id>", "priority": "P1", "type": "Product", "description": "Press 7 reported E42 at 13:55"}}
```

The response is the created Case, including its `id`.

**Note and close**

While the alarm is handled, publish to `crm/sugarcrm/cases/notes/create`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<from TokenGet>", "id": "<case id>", "body": {"name": "Spindle temperature back to 61 C"}}
```

When it clears, publish `{"access_token": "...", "module": "Cases", "id": "<case id>", "body": {"status": "Closed"}}` to `crm/sugarcrm/records/update`. Valid status values come from `crm/sugarcrm/enum/get` with `"module": "Cases", "field": "status"`.

**Open cases for an account**

Publish to `crm/sugarcrm/cases/filter`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<from TokenGet>", "body": {"filter": [{"account_id": "<account id>"}, {"status": {"$not_in": ["Closed", "Rejected"]}}], "fields": "id,name,status,priority", "max_num": 50}}
```

For just the number, send the same filter to `crm/sugarcrm/records/count` with `"module": "Cases"`.

**Upsert by asset id**

With an admin user, keep one record per machine by its serial in `sync_key`. Publish to `crm/sugarcrm/records/sync-key/upsert`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"access_token": "<from TokenGet>", "module": "Accounts", "sync_key": "PLANT-PORTO-01", "body": {"name": "ACME Porto plant", "description": "3 lines online"}}
```

Sugar answers 201 when it created the record and 200 when it updated it, with `{"record": "<id>"}`.

## Receiving changes from Sugar

Sugar pushes record changes out with Web Logic Hooks (**Admin > Web Logic Hooks**): pick a module, a trigger event (for example after save) and a URL, and Sugar sends the record as JSON. Deliveries are queued and sent by the "Dispatch Web Logic Hook" scheduled job, so they arrive on the next cron run, not instantly. Point the hook at a separate server-mode REST\_API connector that enables `ENABLE_SERVER`; Sugar does not sign deliveries, so restrict that endpoint (network or a secret path).

## Troubleshooting

| Symptom | Cause |
| - | - |
| 401 on `TokenGet` | Wrong username or password, or `client_id` / `platform` missing or not registered |
| 401 `invalid_grant` or `need_login` on a call | Access token expired (one hour) or missing; refresh it on `crm/sugarcrm/oauth/refresh` |
| Users get signed out of the web client | The broker logs in with the `base` platform; register and use a dedicated platform |
| 403 `not_authorized` on a `sync_key` event | Those endpoints are admin only |
| 404 `not_found` on a module endpoint | Module names are case-sensitive (`Cases`, not `cases`), or the module, link or record does not exist |
| Empty or partial lists | `max_num` defaults to 20; page with `offset` until `next_offset` is -1 |

## Related

* [REST API connectors](/latest/lot-language/routes/rest-api-routes)
* [REST API app templates](/latest/lot-language/routes/rest-api-apps/index)
* [SugarCRM REST API endpoint reference](https://support.sugarcrm.com/documentation/sugar_developer/sugar_developer_guide_25.2/integration/web_services/rest_api/endpoints/)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.