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.
Overview
Section titled “Overview”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.
Prerequisites
Section titled “Prerequisites”Before you begin, ensure you have:
- Admin access to the CRM system
- Cognito credentials for the admin user
- A clear understanding of which scopes your integration needs
Quick Start
Section titled “Quick Start”Step 1: Create an M2M Client
Section titled “Step 1: Create an M2M Client”A TBC-App admin user needs to create a new M2M client through the CRM web interface:
- Navigate to Settings → API Access
- Click “Create M2M Client”
- 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)
- 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
Step 2: Obtain an Access Token
Section titled “Step 2: Obtain an Access Token”Exchange your credentials for an access token:
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.
Step 3: Make an API Call
Section titled “Step 3: Make an API Call”Use the access token to call the CRM API:
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:
lastKeyis a base64-encoded cursor for the next page- Pass it as
?lastKey=...to get the next page nullwhen there are no more results
Available Scopes
Section titled “Available Scopes”The M2M API is read-only, so these are the scopes your client should request:
| Scope | Description | Permissions |
|---|---|---|
crm-api/products.read | Read product data | GET /products, GET /products/:id, GET /products/search |
crm-api/brands.read | Read brand data | GET /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
.writescopes, and rejected scopes. Cognito also definesproducts.writeandbrands.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 gets403. Scope names for the Quotes domain and for customer (client) records — e.g.quotes.readorclients.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.
Code Examples
Section titled “Code Examples”Node.js
Section titled “Node.js”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 tokenasync 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 productsasync function getProducts() { const accessToken = await getAccessToken();
const response = await axios.get(`${API_ENDPOINT}/products?limit=10`, { headers: { Authorization: `Bearer ${accessToken}` }, });
return response.data;}Python
Section titled “Python”import osimport 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 tokendef 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 productsdef 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()Next Steps
Section titled “Next Steps”- Authentication Guide - Deep dive into OAuth flow
- Client Management - Managing M2M clients
- API Reference - Complete API endpoint documentation
- Code Examples - Full examples in multiple languages
- Troubleshooting - Common issues and solutions
Support
Section titled “Support”If you encounter issues:
- Check the Troubleshooting Guide
- Review the OpenAPI Specification
- Contact API support at support@tbc-app.com