Skip to content

Getting Started with M2M API

The CRM M2M (Machine-to-Machine) API allows external systems to access the CRM programmatically using OAuth 2.0 Client Credentials flow. This guide will walk you through obtaining credentials and making your first API call.

M2M authentication is designed for server-to-server communication where no user interaction is required. Common use cases include:

  • E-commerce integrations (WooCommerce, Shopify, etc.)
  • Automated data synchronization
  • Custom reporting dashboards
  • Third-party integrations
  • Scheduled background jobs

The M2M API is a read-only catalog API. Your integration can read Products and Brands (list, get-by-id, and search) to sync the CRM catalog into another system. Creating or editing records — and the entire Quotes and Templates workflow — is reserved for signed-in web-app users and is not available to M2M clients. See What’s available via M2M.

Before you begin, ensure you have:

  1. Admin access to the CRM system
  2. Cognito credentials for the admin user
  3. A clear understanding of which scopes your integration needs

A TBC-App admin user needs to create a new M2M client through the CRM web interface:

  1. Navigate to Settings → API Access
  2. Click “Create M2M Client”
  3. Fill in the details:
    • Client Name: Descriptive name (e.g., “WooCommerce Integration”)
    • Description: Purpose of this client
    • Scopes: Select the read scopes your integration needs (e.g., products.read, brands.read)
    • Expiration: Optional expiration date (1-365 days)
  4. Click “Create”

⚠️ CRITICAL: The client secret is displayed ONLY ONCE. Copy and store it securely immediately. If lost, you must create a new client.

You’ll receive:

  • Client ID: 26-character identifier (e.g., abc123xyz456def789ghi012)
  • Client Secret: Long random string (shown only once)
  • Token Endpoint: Cognito OAuth URL

Exchange your credentials for an access token:

Terminal window
curl -X POST TOKEN_ENDPOINT \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "scope=crm-api/products.read"

Response:

{
"access_token": "eyJraWQiOiJ...<JWT>...9zA",
"token_type": "Bearer",
"expires_in": 3600
}

Token Lifetime: Access tokens are valid for 1 hour (3600 seconds) for clients created from now on (clients created earlier keep 24 hours). Use expires_in from the token response. Implement token caching and refresh logic in your application.

Use the access token to call the CRM API:

Terminal window
curl -X GET "https://{api-base-url}/products?limit=10" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response:

{
"products": [
{
"id": "ac1198a7-33f9-460c-b648-5cbbf9841869",
"productCode": "PRD-XQEPWD",
"brandName": "Wanabiwood Flooring",
"category": "Beauty",
"description": "Advanced technology increases capabilities",
"price": 3227.15,
"createdAt": "2025-11-21T15:56:18.847Z"
}
],
"lastKey": "eyJpZCI6ImFjMTE5OGE3LTMzZjktNDYwYy1iNjQ4LTVjYmJmOTg0MTg2OSJ9"
}

Pagination:

  • lastKey is a base64-encoded cursor for the next page
  • Pass it as ?lastKey=... to get the next page
  • null when there are no more results

The M2M API is read-only, so these are the scopes your client should request:

ScopeDescriptionPermissions
crm-api/products.readRead product dataGET /products, GET /products/:id, GET /products/search
crm-api/brands.readRead brand dataGET /brands, GET /brands/:id, GET /brands/search

Best Practice: Request only the scopes your integration actually needs (principle of least privilege).

Product and brand responses are also projected through a fixed, server-owned field set — cost and margin fields are never returned and the set cannot be widened by the caller. See Field policy for the complete list of returned fields.

About the .write scopes, and rejected scopes. Cognito also defines products.write and brands.write, and an admin can grant them to a client, but they are not usable from an M2M token: write operations require an admin web-app session, so an M2M token still gets 403. Scope names for the Quotes domain and for customer (client) records — e.g. quotes.read or clients.read — are rejected outright when an administrator attempts to grant them; they are not part of the grantable set at all. Request only the read scopes above.

import axios from 'axios';
// Configuration - Load from environment variables (no defaults)
const OAUTH_TOKEN_ENDPOINT = process.env.OAUTH_TOKEN_ENDPOINT;
const API_ENDPOINT = process.env.API_ENDPOINT;
const CLIENT_ID = process.env.OAUTH_CLIENT_ID;
const CLIENT_SECRET = process.env.OAUTH_CLIENT_SECRET;
if (!OAUTH_TOKEN_ENDPOINT || !API_ENDPOINT || !CLIENT_ID || !CLIENT_SECRET) {
console.error('Missing required environment variables');
process.exit(1);
}
// Get access token
async function getAccessToken() {
const params = new URLSearchParams({
grant_type: 'client_credentials',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
scope: 'crm-api/products.read',
});
const response = await axios.post(
OAUTH_TOKEN_ENDPOINT,
params,
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
return response.data.access_token;
}
// Get products
async function getProducts() {
const accessToken = await getAccessToken();
const response = await axios.get(`${API_ENDPOINT}/products?limit=10`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
return response.data;
}
import os
import requests
# Configuration - Load from environment variables (no defaults)
OAUTH_TOKEN_ENDPOINT = os.environ['OAUTH_TOKEN_ENDPOINT']
API_ENDPOINT = os.environ['API_ENDPOINT']
CLIENT_ID = os.environ['OAUTH_CLIENT_ID']
CLIENT_SECRET = os.environ['OAUTH_CLIENT_SECRET']
# Get access token
def get_access_token():
response = requests.post(
OAUTH_TOKEN_ENDPOINT,
data={
'grant_type': 'client_credentials',
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'scope': 'crm-api/products.read',
},
headers={'Content-Type': 'application/x-www-form-urlencoded'},
)
return response.json()['access_token']
# Get products
def get_products():
access_token = get_access_token()
response = requests.get(
f'{API_ENDPOINT}/products',
params={'limit': 10},
headers={'Authorization': f'Bearer {access_token}'},
)
return response.json()

If you encounter issues:

  1. Check the Troubleshooting Guide
  2. Review the OpenAPI Specification
  3. Contact API support at support@tbc-app.com