Getting Started
Overview
The Team Common Ground Activity API allows you to send events from external systems to automatically create activities for your clients. This is useful for integrating with payment processors, CRMs, scheduling tools, and other business applications.
Quick Start
Generate an API key from your integration settings, then send a test request using the API playground. Once verified, integrate the API calls into your application.
Payload Limits
- Max payload size: 100KB
Base URL
https://api.teamcommonground.com/v1/activitiesCode Examples
curl -X POST https://api.teamcommonground.com/v1/activities \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"type": "c_order",
"source": "my-app",
"client": {
"email": "customer@example.com"
},
"message": "Order #order_123 for $99.00"
}'Authentication
API Keys
All requests must include an API key in the Authorization header:
Authorization: Bearer your_api_key_here
Security Best Practices
- Never expose API keys in client-side code
- Rotate keys periodically using the Rotate function
- Use the API Playground to test payloads before integrating
- Revoke compromised keys immediately
IP Allowlisting
You can restrict API keys to only accept requests from specific IP addresses. This prevents stolen keys from being used from unauthorized locations. IP restrictions are configured per API key in your integration settings.
Supported Formats:
- Single IPv4:
203.0.113.50 - IPv4 CIDR:
10.0.0.0/8,192.168.1.0/24 - Single IPv6:
2001:db8::1 - IPv6 CIDR:
2001:db8::/32
IP Blocked Response (403):
{
"error": "IP not allowed",
"code": "IP_NOT_ALLOWED",
"clientIp": "45.67.89.10"
}
API Payload
Payload Structure
Send a POST request to https://api.teamcommonground.com/v1/activities with a JSON body:
{
"type": "c_order",
"source": "my-app",
"client": {
"id": "cust_123",
"email": "customer@example.com",
"phone": "+1234567890"
},
"message": "Order #order_456 for $99.00",
"tags": ["tag_abc123", "tag_xyz789"],
"links": ["https://example.com/order/456"],
"visibility": ["role:admin", "role:staff", "role:client"],
"createdBy": "user_abc123"
}
Required Fields
| Field | Type | Description |
|---|---|---|
| type | string | Activity type (e.g., note, task_created, c_order) |
Optional Fields
| Field | Type | Description |
|---|---|---|
| source | string | Unique identifier for your integration (e.g., "zapier", "shopify", "my-crm"). Must be lowercase alphanumeric with hyphens or underscores only. Max 50 characters. |
| client | object | Client lookup data (see Client Object below) |
| message | string | Display text for the activity |
| tags | string[] | Array of tag IDs from your business's tags. Unrecognized tags are ignored in the UI |
| links | string[] | Array of URLs to attach as link previews. The API automatically fetches rich previews (title, description, image) for each URL |
| visibility | string[] | Who can see: role prefixes (role:admin, role:staff, role:client) and user prefixes (user:user_id). Defaults to ["role:owner", "role:admin", "role:staff"]. Note: role:owner is always included automatically |
| createdBy | string | User ID of creator (defaults to "system") |
Source Field
The source field identifies which integration sent the request. Use a unique, descriptive name for each integration:
my-internal-app- Your custom applicationmy-crm- Your CRM systemshopify- Your Shopify store
This enables:
- Filtering API logs by source
- Source-specific client ID mappings (same external ID can map to different clients per source)
- Clear identification of where each activity originated
Display Name
The source is used to look up your integration configuration. If you've set a custom name for your integration, that name will be displayed in the activity feed and logs.
If no custom name is set, the source is automatically capitalized for display (e.g., goku → Goku).
Example: If you have an integration with sourceId: "my-crm" and customName: "Acme CRM", activities will show "Acme CRM" instead of "my-crm" in the UI.
Client Object
The client object is used to match a client:
| Field | Type | Description |
|---|---|---|
| id | string | Your client ID from your system (highest priority for matching) |
| string | Client email (used for matching) | |
| phone | string | Client phone number (used for matching) |
| firstName | string | Client first name (used when auto-creating clients) |
| lastName | string | Client last name (used when auto-creating clients) |
At least one of id, email, or phone is required for client matching.
The id field lets you reference clients by your internal identifier. We store a mapping so future requests with the same id will link to the same client.
Visibility
Control who can see the activity using the visibility field. If not specified, defaults to ["role:owner", "role:admin", "role:staff"] (team only).
Important: Business owners (role:owner) always have visibility to all API activities. The role:owner is automatically added to every activity's visibility, so you don't need to include it in your requests.
Format:
The visibility array uses prefixed entries:
- Role prefixes (
role:):role:admin,role:staff,role:client - User prefixes (
user:):user:user_idfor specific users
| Prefix | Description |
|---|---|
role:admin | Business administrators |
role:staff | Staff members |
role:client | All clients of the business |
user:id | Specific user by their user ID (Firebase Auth UID, not client ID) |
Examples:
Team only (default):
{
"visibility": ["role:admin", "role:staff"]
}
Team + all clients:
{
"visibility": ["role:admin", "role:staff", "role:client"]
}
Team + specific client:
{
"visibility": ["role:admin", "role:staff", "user:abc123xyz"]
}
Important:
role:owneris always included automatically - you don't need to add it- Adding
role:clientmakes the activity visible to all clients of the business - To share with a specific client, use
user:user_idwith the client's user ID (Firebase Auth UID). This is not the same as the client record ID. You can find the user ID in the client'suserIdfield - When sharing with clients (
role:client), always include team roles too
External ID Mapping:
For custom integrations, you can use your external client IDs in visibility entries. If you have a client ID mapping set up (via the client.id field), the same mapping is applied to visibility:
{
"visibility": ["role:admin", "role:staff", "user:cust_12345"]
}
If cust_12345 is mapped to an internal client with user ID abc123xyz, the visibility will be automatically converted to ["role:owner", "role:admin", "role:staff", "user:abc123xyz"].
This allows you to use consistent external IDs across both client matching and visibility without needing to know the internal user IDs.
Client Matching
Clients are matched in this order:
id- Exact match on your client ID (requires pre-mapping)email- Case-insensitive email matchphone- Normalized phone number match
If no match is found, the activity is still created but without a client association. You can view unlinked activities in the API logs and manually link them to clients.
For more details on how client matching works, see the section.
Links
Attach external URLs to activities by including a links array. The API automatically fetches rich link previews for each URL.
{
"type": "note",
"source": "my-app",
"client": {
"email": "customer@example.com"
},
"message": "Check out this article",
"links": [
"https://example.com/article",
"https://www.youtube.com/watch?v=dQw4w9WgXcQ"
]
}
Link Preview Features
- Open Graph & Twitter Cards - Extracts title, description, and image from og: and twitter: meta tags
- YouTube Videos - Shows video thumbnail, title, and channel name
- Twitter/X Posts - Displays tweet text and media
- Fallback Handling - Failed URLs still create a minimal preview with the URL and domain
Limits
- Link preview fetching has a 5 second timeout per URL
- Total payload size including links must be under 100KB
Client Mapping
Overview
When you send an API request, Team Common Ground automatically matches the activity to a client in your business. This section explains how client matching works and how to set up mappings for your external client IDs.
Automatic Matching
We automatically match clients using the email and phone fields from the API payload:
Email Matching
If you include an email in the client object, we'll find any client in your business with that email address:
{
"client": {
"email": "john@example.com"
}
}
Email matching is case-insensitive, so John@Example.com matches john@example.com.
Phone Matching
If you include a phone in the client object, we'll normalize and match it:
{
"client": {
"phone": "+1 (555) 123-4567"
}
}
Phone numbers are normalized before matching. All of these will match the same client:
+15551234567(555) 123-4567555-123-45675551234567
External ID Mapping
For systems that use their own customer IDs (e.g., ABCDE12345 from your CRM, or contact IDs from external tools), you can pre-map these IDs to your clients.
Why Use ID Mapping?
- Reliability: Email or phone might not always be available in API payloads
- Consistency: Use your existing customer IDs from other systems
- Performance: ID lookups are faster than email/phone searches
Setting Up Mappings
Client ID mappings are managed in the integration settings under Mappings. Each mapping consists of:
- Source: The system name (e.g.,
my-crm,shopify) - External ID: The ID from your external system (e.g.,
ABCDE12345) - Client: The client this ID maps to
Using Mapped IDs in API Requests
Once mapped, include the id and optionally source in your request:
{
"source": "my-crm",
"client": {
"id": "ABCDE12345"
}
}
The source field is required and tells us which system this ID comes from. This allows you to have different client ID mappings for each integration source.
Matching Priority
When multiple identifiers are provided, we match in this order:
- External ID (
client.id+source) - Looks up pre-configured mappings - Email (
client.email) - Searches clients by email address - Phone (
client.phone) - Searches clients by phone number
The first match wins. If you provide all three and the ID mapping exists, we use that client regardless of email/phone.
Example: Complete Request
{
"type": "c_order",
"source": "my-crm",
"client": {
"id": "ABCDE12345",
"email": "john@example.com",
"phone": "+15551234567"
},
"message": "Order #order_456 for $99.00"
}
Matching behavior:
- First, checks if
my-crm:ABCDE12345has a mapping → uses that client - If no mapping, searches for a client with email
john@example.com - If no email match, searches for a client with phone
+15551234567 - If no match found, activity is created without a client association
Unmatched Activities
By default, if no client is matched:
- The activity is still created and logged
- It appears in the Monitoring tab with status "No client matched"
- You can manually link it to a client from the API logs
- Consider adding a mapping for future requests with the same ID
Event Settings
Each integration has configurable event settings that control how unmatched activities are handled:
Client Matching Options
- All activities (default) - Log all incoming activities, even without a client match
- Only activities matching existing clients - Skip activities where no client is found
Auto-Create Clients
When "All activities" is selected, you can enable Auto-create clients from client.id:
- When
client.idis provided but doesn't match any existing client, a new client is automatically created - The new client is linked with the external ID mapping for future requests
emailandphoneare saved to the client record if provided
Client Name Resolution:
When auto-creating a client, the name is derived from the payload fields in this priority order:
| Field | Priority | Fallback |
|---|---|---|
| First Name | firstName → email → phone → id | Uses the first available value |
| Last Name | lastName | null if not provided |
Example: If your payload has {"id": "cust_123", "email": "john@example.com"} but no firstName, the client will be created with first name "john@example.com".
This is useful when you want to automatically onboard new customers from your external system without manually creating client records.
Activity Types
System Activity Types
Use these built-in activity types for common integrations:
Notes & Communication
| Type | Description |
|---|---|
note | General note or log entry for a client |
business_note | Business-wide internal note (not client-specific) |
business_update | Business announcement or status update |
email | Email communication log |
message | Direct message or chat |
Tasks & Scheduling
| Type | Description |
|---|---|
task_created | Task assignment |
follow_up_created | Client follow-up item |
Tasks and follow-ups support due dates:
{
"type": "task_created",
"message": "Review proposal draft",
"dueAt": "2024-02-15T10:00:00Z",
"hasTime": true
}
Issues
| Type | Description |
|---|---|
report_issue_created | Issue or bug report from a client |
Client Management
| Type | Description |
|---|---|
client_added | New client record created |
Custom Activity Types
For events not covered by system types, use custom activity types with the c_ prefix.
Custom activity types are defined in your business settings with a unique internalId (slug). For example, if you created a "Site Visit" type, its internalId would be c_site_visit.
{
"type": "c_order",
"source": "shopify",
"client": {
"email": "customer@example.com"
},
"message": "Order #12345 placed",
"data": {
"orderId": "12345",
"total": 99.99
}
}
Custom types can have custom icons and colors configured in your activity type settings.
Threading
Overview
Create threaded conversations by linking activities together. Replies to activities share a common threadId for efficient querying and thread-level operations.
Threading Fields
| Field | Type | Description |
|---|---|---|
threadId | string | Thread identifier (required for replies) |
parentActivityId | string | Direct parent activity to reply to (optional) |
Creating a Reply
To reply to an existing activity, include both threadId and parentActivityId in your payload:
{
"type": "message",
"source": "my-app",
"threadId": "activity_abc123",
"parentActivityId": "activity_abc123",
"client": {
"email": "customer@example.com"
},
"message": "Thanks for your message! We'll look into this."
}
Field Details:
threadId: The root activity's ID (start of the thread). All activities in a thread share this samethreadId.parentActivityId: The specific activity being replied to. Can be the same asthreadIdfor direct replies to the root, or a different activity ID for nested replies.
Thread Behavior
- Root activities: When you create a new activity without
threadId, it becomes a root activity andthreadIdis automatically set to the activity's own ID. - Replies: Must include
threadIdto be part of the thread. Replies inherit visibility from their parent by default. - Thread queries: All activities in a thread can be efficiently retrieved using the shared
threadId. - Notifications: Include
threadIdfor client-side thread filtering and notification suppression.
Examples
Reply to Root Activity
{
"type": "message",
"source": "my-app",
"threadId": "root_activity_id",
"parentActivityId": "root_activity_id",
"client": { "email": "customer@example.com" },
"message": "Replying directly to the root"
}
Nested Reply
{
"type": "message",
"source": "my-app",
"threadId": "root_activity_id",
"parentActivityId": "previous_reply_id",
"client": { "email": "customer@example.com" },
"message": "Replying to a previous reply in the thread"
}
Thread Notifications
When activities are created as part of a thread, notifications include the threadId in the payload data. This allows client applications to:
- Suppress notifications when viewing the active thread
- Auto-mark notifications as read for the current thread
- Group notifications by thread
Best Practices
- Always include threadId for replies: Ensures replies are properly linked to the thread.
- Use parentActivityId for reply context: Helps build the conversation tree structure.
- Inherit visibility: Let replies inherit visibility from the parent to maintain consistent access control.
- Store threadId on root activities: Root activities should have
threadIdequal to their own ID for consistency.
Response Codes
Success Responses
201 Created
The activity was created successfully:
{
"status": "success",
"activityId": "activity-id-123",
"requestId": "abc123",
"client": {
"status": "matched",
"clientId": "client-id-456",
"matchedBy": "email"
}
}
Error Responses
400 Bad Request
Invalid payload format:
{
"error": "Invalid payload",
"code": "INVALID_PAYLOAD",
"details": ["type is required"]
}
401 Unauthorized
Missing or invalid API key:
{
"error": "Invalid API key",
"code": "INVALID_API_KEY"
}
429 Too Many Requests
Rate limit exceeded:
{
"error": "Rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED",
"retryAfter": 60
}
Headers include rate limit info:
X-RateLimit-Limit: Requests allowed per minuteX-RateLimit-Remaining: Requests remainingRetry-After: Seconds until reset
Platform Integrations
Overview
Platform integrations let you connect with your clients directly through Team Common Ground.
Available Integrations
| Platform | Description |
|---|---|
| 💬 Chat | Embeddable chat for your website |
| 📋 Survey | Collect client feedback via surveys |
How Integrations Work
Integrations are managed in your business settings. Each integration can be configured to match your workflow and client engagement needs.
Embed Integration
Overview
The embed integration allows partners to add activity buttons (like "Report Bug" or "Send Feedback") to their websites. When users click the button, a popup window opens where they can authenticate and submit activities directly to your business.
How It Works
A button on a partner website opens a Team Common Ground popup. Users sign in (or create an account), submit the activity form, and are automatically added as a client if they're new to the business.
Requirements
- Verified domain - The website domain must be verified (see Domain Verification below)
- Business ID - Available in your integration settings
URL Structure
https://www.teamcommonground.com/embed/activity
?bid={businessId} # Required: your business ID
&type={activityType} # Required: activity type
&message={message} # Optional: pre-fill the message
&invite=true # Optional: send invite to new clients
&return={returnUrl} # Optional: redirect after success
Parameters
| Parameter | Required | Description |
|---|---|---|
bid | Yes | Your business ID |
type | Yes | Activity type (see Activity Types below) |
message | No | Pre-filled message text |
invite | No | Set to true to send invite email to new clients |
return | No | URL to redirect to after success (defaults to parent page) |
Activity Types
| Type | Description |
|---|---|
report_issue_created | Bug report or issue |
feedback_created | General feedback |
note | Generic note |
Domain Verification
Before the embed popup accepts submissions from your website, you must verify your domain. Two verification methods are supported:
File-based Verification
Host a verification file at /.well-known/teamcommonground.json on your domain:
{
"verification": {
"key": "your_verification_key_here"
}
}
DNS TXT Record Verification
If you can't host files on your web server, add a DNS TXT record:
- Name/Host:
_teamcommonground-verify(or_teamcommonground-verify.yourdomain.comdepending on your DNS provider) - Value:
teamcommonground-verify=your_verification_key_here
DNS propagation can take a few minutes to 48 hours. Both methods ensure only authorized websites can display the embed popup.
Implementation
Basic Button
<button onclick="window.open(
'https://www.teamcommonground.com/embed/activity?bid=YOUR_BUSINESS_ID&type=report_issue_created',
'tcg-activity',
'width=480,height=640'
)">
Report Issue
</button>
With Pre-filled Message
<button onclick="window.open(
'https://www.teamcommonground.com/embed/activity?bid=YOUR_BUSINESS_ID&type=feedback_created&message=I%20noticed%20an%20issue%20with...',
'tcg-activity',
'width=480,height=640'
)">
Send Feedback
</button>
With Auto-Invite
Send an invitation email to new users when they submit their first activity:
<button onclick="window.open(
'https://www.teamcommonground.com/embed/activity?bid=YOUR_BUSINESS_ID&type=report_issue_created&invite=true',
'tcg-activity',
'width=480,height=640'
)">
Report Bug
</button>
JavaScript Helper Function
function openTCGPopup(activityType, options = {}) {
const baseUrl = 'https://www.teamcommonground.com/embed/activity';
const params = new URLSearchParams({
bid: 'YOUR_BUSINESS_ID',
type: activityType,
});
if (options.message) {
params.set('message', options.message);
}
if (options.invite) {
params.set('invite', 'true');
}
if (options.returnUrl) {
params.set('return', options.returnUrl);
}
window.open(
`${baseUrl}?${params.toString()}`,
'tcg-activity',
'width=480,height=640'
);
}
// Usage
openTCGPopup('report_issue_created', { message: 'Found a bug...' });
openTCGPopup('feedback_created', { invite: true });
React Component
function ReportButton({ businessId }: { businessId: string }) {
const openPopup = () => {
const url = `https://www.teamcommonground.com/embed/activity?bid=${businessId}&type=report_issue_created`;
window.open(url, 'tcg-activity', 'width=480,height=640');
};
return <button onClick={openPopup}>Report Issue</button>;
}
User Flow
The embed popup handles authentication, form submission, client creation, and optional invite emails automatically. On mobile devices, window.open() opens a new browser tab instead of a popup. After successful submission, the window automatically closes after 3 seconds.
Security
- Domain Verification Required: The Referer header must match a verified domain for your business
- Authentication Required: Users must sign in before submitting
- Rate Limiting: Standard API rate limits apply
Troubleshooting
| Issue | Solution |
|---|---|
| "Domain not verified" | Verify your domain in the integration settings |
| "Business not found" | Check your Business ID is correct |
| Popup blocked | Ensure the popup is triggered by a user action (button click) |
| Activity not appearing | Check the activity feed and API logs for errors |