Developer API

API Documentation

Integrate TC Northwest data into your existing tools, CRMs, and workflows. Our REST API provides read-only access to all your transaction data with space-scoped security and rate limiting.

What can you do with the API?

Connect your TC Northwest data to the tools your business already uses.

Sync to Your CRM

Pull transaction files, contacts, and agent data into Salesforce, HubSpot, Follow Up Boss, or any CRM that accepts REST data.

Real-Time Webhooks

Get notified instantly when files are created, tasks are completed, or contingencies change. Build automations with Zapier, Make, or custom code.

Custom Reporting

Export transaction data to build custom dashboards in Google Sheets, Airtable, or business intelligence tools like Looker or Metabase.

Phone Lookup

Look up contacts by phone number from your phone system, call center, or any application that handles incoming calls.

Workflow Automation

Trigger actions in other systems when events happen in your Space. Send Slack messages, create Trello cards, or update spreadsheets automatically.

MLS Integration

Pull your contact and file data to cross-reference with MLS feeds, keeping your transaction records in sync with listing data.

Getting Started

1

Generate an API Key

API keys are created from your Space Settings page. Each key is scoped to a single Space and provides read-only access to that Space's data. Only the Space owner can create, view, and revoke API keys.

Security Note

Your API key is shown only once when created. Store it securely. If compromised, revoke it immediately from Settings and create a new one. Never share API keys in client-side code, public repositories, or URLs.

2

Authenticate Your Requests

Include your API key in the X-API-Key header with every request.

bash
curl -H "X-API-Key: tc_live_your_api_key_here" \
  https://your-space.tcnorthwest.com/api/v1/files
3

Make Your First Request

Start by checking the health endpoint to verify your connection, then query your data.

bash
# Check API health (no auth required)
curl https://your-space.tcnorthwest.com/api/v1/health

# List your transaction files
curl -H "X-API-Key: tc_live_your_api_key_here" \
  https://your-space.tcnorthwest.com/api/v1/files

# Get a specific file by ID
curl -H "X-API-Key: tc_live_your_api_key_here" \
  https://your-space.tcnorthwest.com/api/v1/files/12345

# Search contacts by name
curl -H "X-API-Key: tc_live_your_api_key_here" \
  "https://your-space.tcnorthwest.com/api/v1/contacts?search=Smith"

# Lookup a contact by phone number
curl -H "X-API-Key: tc_live_your_api_key_here" \
  "https://your-space.tcnorthwest.com/api/v1/contacts/lookup?phone=2065551234"

API Overview

Authentication

API key via X-API-Key header. Keys are scoped to a single Space with read-only access. All data is automatically filtered to the Space associated with your key.

Rate Limiting

60 requests per minute per API key. Rate limit headers are included in every response:X-RateLimit-Limit andX-RateLimit-Remaining.

Space Scoping

Every API key is bound to a Space. All queries are automatically scoped to that Space's data. You cannot access data from other Spaces, even with a valid key. Sensitive fields (password hashes, signing secrets) are excluded from responses.

Read-Only Access

API keys provide read-only access (GET requests only). Write operations (POST, PATCH, DELETE) return a 403 Forbidden response. The only exception is the Webhooks API, which supports full CRUD for managing webhook subscriptions.

Response Format

All responses follow a consistent JSON envelope format:

json
// Success response
{
  "success": true,
  "data": [ ... ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 142,
    "totalPages": 6
  }
}

// Error response
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or expired API key"
  }
}

Query Parameters

All list endpoints support the following query parameters for pagination, sorting, filtering, and search:

ParameterTypeDefaultDescription
pageinteger1Page number for pagination
pageSizeinteger25Items per page (max 100)
sortstringidColumn name to sort by
orderstringasc"asc" or "desc"
searchstringFull-text search across text columns
{column}variesFilter by any column value (e.g., status=active)

Error Codes

HTTP StatusCodeDescription
401UNAUTHORIZEDMissing or invalid API key
403FORBIDDENWrite operation attempted with read-only key
404NOT_FOUNDResource not found or not in your Space
400VALIDATION_ERRORInvalid request parameters
429RATE_LIMIT_EXCEEDEDToo many requests (60/min limit)
500INTERNAL_ERRORServer error — contact support

API Endpoints

All endpoints are prefixed with /api/v1. Data is automatically scoped to the Space associated with your API key.

GET
/api/v1/health

Check API availability. No authentication required.

json
// Response
{ "status": "ok", "timestamp": "2026-03-10T12:00:00.000Z" }

Data Endpoints

Each entity has two read-only endpoints: a list endpoint with pagination, sorting, filtering, and search, and a detail endpoint that returns a single record by ID.

Webhooks

Subscribe to real-time events from your Space. When something happens (a file is created, a task is completed, etc.), we send an HTTP POST to your configured URL with the event data. Webhook payloads are signed with HMAC-SHA256 so you can verify authenticity.

Webhook Management

GET
/api/v1/webhooks

List all your webhook subscriptions

POST
/api/v1/webhooks

Create a new webhook subscription

GET
/api/v1/webhooks/:id

Get a specific webhook

PATCH
/api/v1/webhooks/:id

Update a webhook (URL, events, active status)

DELETE
/api/v1/webhooks/:id

Delete a webhook subscription

POST
/api/v1/webhooks/:id/test

Send a test event to verify your endpoint

GET
/api/v1/webhooks/:id/deliveries

View delivery history and retry status

Creating a Webhook

bash
curl -X POST \
  -H "X-API-Key: tc_live_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My CRM Sync",
    "url": "https://your-app.com/webhooks/tc-northwest",
    "events": ["file.created", "file.updated", "contact.created"]
  }' \
  https://your-space.tcnorthwest.com/api/v1/webhooks

# Response (signing secret shown only once!)
{
  "success": true,
  "data": {
    "id": "wh_42",
    "name": "My CRM Sync",
    "url": "https://your-app.com/webhooks/tc-northwest",
    "events": ["file.created", "file.updated", "contact.created"],
    "signingSecret": "whsec_abc123...",
    "isActive": true,
    "createdAt": "2026-03-10T12:00:00.000Z"
  }
}

Available Events

User Events

user.createduser.updateduser.deleted

Contact Events

contact.createdcontact.updatedcontact.deleted

File Events

file.createdfile.updatedfile.deleted

Task Events

task.createdtask.updatedtask.completedtask.deleted

Calendar Events

calendar_item.createdcalendar_item.updatedcalendar_item.deleted

Contingency Events

contingency.createdcontingency.updatedcontingency.deleted

Note Events

note.creatednote.updatednote.deleted

Verifying Webhook Signatures

Each webhook delivery includes a signature in the headers. Verify it using HMAC-SHA256 with your signing secret:

javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
app.post('/webhooks/tc-northwest', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    'whsec_your_signing_secret'
  );
  
  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process the event
  const { event, data, timestamp } = req.body;
  console.log(`Received ${event}:`, data);
  
  res.status(200).send('OK');
});

Code Examples

python
import requests

API_KEY = "tc_live_your_api_key_here"
BASE_URL = "https://your-space.tcnorthwest.com/api/v1"

headers = {"X-API-Key": API_KEY}

# Fetch all active files
response = requests.get(f"{BASE_URL}/files", headers=headers)
data = response.json()

if data["success"]:
    for file in data["data"]:
        print(f"File #{file['id']}: {file['propertyAddress']}")
        print(f"  Status: {file['listingStatus']}")
        print(f"  Type: {file['fileType']}")
        print()
    
    print(f"Total: {data['pagination']['total']} files")
else:
    print(f"Error: {data['error']['message']}")

Ready to integrate?

Generate your API key from your Space Settings to start building integrations today.

Read the docs again