CompanyCam
Access the CompanyCam API with managed OAuth authentication. Manage projects, photos, users, tags, groups, documents, and webhooks for contractor photo documentation.
Quick Start
# List projects
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/companycam/v2/projects')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Base URL
https://gateway.maton.ai/companycam/v2/{resource}
Replace {resource} with the actual CompanyCam API endpoint path. The gateway proxies requests to api.companycam.com/v2 and automatically injects your OAuth token.
Authentication
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEY
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Getting Your API Key
- Sign in or create an account at maton.ai
- Go to maton.ai/settings
- Copy your API key
Connection Management
Manage your CompanyCam OAuth connections at https://ctrl.maton.ai.
List Connections
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections?app=companycam&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Create Connection
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'companycam'}).encode()
req = urllib.request.Request('https://ctrl.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Get Connection
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"connection": {
"connection_id": "d274cf68-9e76-464c-92e3-ff274c44526e",
"status": "ACTIVE",
"creation_time": "2026-02-12T01:56:32.259046Z",
"last_updated_time": "2026-02-12T01:57:38.944271Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "companycam",
"metadata": {}
}
}
Open the returned url in a browser to complete OAuth authorization.
Delete Connection
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Specifying Connection
If you have multiple CompanyCam connections, specify which one to use with the Maton-Connection header:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/companycam/v2/projects')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', 'd274cf68-9e76-464c-92e3-ff274c44526e')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If omitted, the gateway uses the default (oldest) active connection.
API Reference
Company
Get Company
GET /companycam/v2/company
Returns the current company information.
Users
Get Current User
GET /companycam/v2/users/current
List Users
GET /companycam/v2/users
Query parameters:
page- Page numberper_page- Results per page (default: 25)status- Filter by status (active, inactive)
Create User
POST /companycam/v2/users
Content-Type: application/json
{
"first_name": "John",
"last_name": "Doe",
"email_address": "john@example.com",
"user_role": "standard"
}
User roles: admin, standard, limited
Get User
GET /companycam/v2/users/{id}
Update User
PUT /companycam/v2/users/{id}
Content-Type: application/json
{
"first_name": "John",
"last_name": "Smith"
}
Delete User
DELETE /companycam/v2/users/{id}
Projects
List Projects
GET /companycam/v2/projects
Query parameters:
page- Page numberper_page- Results per page (default: 25)query- Search querystatus- Filter by statusmodified_since- Unix timestamp for filtering
Create Project
POST /companycam/v2/projects
Content-Type: application/json
{
"name": "New Construction Project",
"address": {
"street_address_1": "123 Main St",
"city": "Los Angeles",
"state": "CA",
"postal_code": "90210",
"country": "US"
}
}
Get Project
GET /companycam/v2/projects/{id}
Update Project
PUT /companycam/v2/projects/{id}
Content-Type: application/json
{
"name": "Updated Project Name"
}
Delete Project
DELETE /companycam/v2/projects/{id}
Archive Project
PATCH /companycam/v2/projects/{id}/archive
Restore Project
PUT /companycam/v2/projects/{id}/restore
Project Photos
List Project Photos
GET /companycam/v2/projects/{project_id}/photos
Query parameters:
page- Page numberper_page- Results per pagestart_date- Filter by start date (Unix timestamp)end_date- Filter by end date (Unix timestamp)user_ids- Filter by user IDsgroup_ids- Filter by group IDstag_ids- Filter by tag IDs
Add Photo to Project
POST /companycam/v2/projects/{project_id}/photos
Content-Type: application/json
{
"uri": "https://example.com/photo.jpg",
"captured_at": 1609459200,
"coordinates": {
"lat": 34.0522,
"lon": -118.2437
},
"tags": ["exterior", "front"]
}
Project Comments
List Project Comments
GET /companycam/v2/projects/{project_id}/comments
Add Project Comment
POST /companycam/v2/projects/{project_id}/comments
Content-Type: application/json
{
"comment": {
"content": "Work completed successfully"
}
}
Project Labels
List Project Labels
GET /companycam/v2/projects/{project_id}/labels
Add Labels to Project
POST /companycam/v2/projects/{project_id}/labels
Content-Type: application/json
{
"labels": ["priority", "urgent"]
}
Delete Project Label
DELETE /companycam/v2/projects/{project_id}/labels/{label_id}
Project Documents
List Project Documents
GET /companycam/v2/projects/{project_id}/documents
Upload Document
POST /companycam/v2/projects/{project_id}/documents
Content-Type: application/json
{
"uri": "https://example.com/document.pdf",
"name": "Contract.pdf"
}
Project Checklists
List Project Checklists
GET /companycam/v2/projects/{project_id}/checklists
Create Checklist from Template
POST /companycam/v2/projects/{project_id}/checklists
Content-Type: application/json
{
"checklist_template_id": "template_id"
}
Get Project Checklist
GET /companycam/v2/projects/{project_id}/checklists/{checklist_id}
Project Users
List Assigned Users
GET /companycam/v2/projects/{project_id}/assigned_users
Assign User to Project
PUT /companycam/v2/projects/{project_id}/assigned_users/{user_id}
Project Collaborators
List Collaborators
GET /companycam/v2/projects/{project_id}/collaborators
Photos
List All Photos
GET /companycam/v2/photos
Query parameters:
page- Page numberper_page- Results per page
Get Photo
GET /companycam/v2/photos/{id}
Update Photo
PUT /companycam/v2/photos/{id}
Content-Type: application/json
{
"photo": {
"captured_at": 1609459200
}
}
Delete Photo
DELETE /companycam/v2/photos/{id}
List Photo Tags
GET /companycam/v2/photos/{id}/tags
Add Tags to Photo
POST /companycam/v2/photos/{id}/tags
Content-Type: application/json
{
"tags": ["exterior", "completed"]
}
List Photo Comments
GET /companycam/v2/photos/{id}/comments
Add Photo Comment
POST /companycam/v2/photos/{id}/comments
Content-Type: application/json
{
"comment": {
"content": "Great progress!"
}
}
Tags
List Tags
GET /companycam/v2/tags
Create Tag
POST /companycam/v2/tags
Content-Type: application/json
{
"display_value": "Exterior",
"color": "#FF5733"
}
Get Tag
GET /companycam/v2/tags/{id}
Update Tag
PUT /companycam/v2/tags/{id}
Content-Type: application/json
{
"display_value": "Interior",
"color": "#3498DB"
}
Delete Tag
DELETE /companycam/v2/tags/{id}
Groups
List Groups
GET /companycam/v2/groups
Create Group
POST /companycam/v2/groups
Content-Type: application/json
{
"name": "Roofing Team"
}
Get Group
GET /companycam/v2/groups/{id}
Update Group
PUT /companycam/v2/groups/{id}
Content-Type: application/json
{
"name": "Updated Team Name"
}
Delete Group
DELETE /companycam/v2/groups/{id}
Checklists
List All Checklists
GET /companycam/v2/checklists
Query parameters:
page- Page numberper_page- Results per pagecompleted- Filter by completion status (true/false)
Webhooks
List Webhooks
GET /companycam/v2/webhooks
Create Webhook
POST /companycam/v2/webhooks
Content-Type: application/json
{
"url": "https://example.com/webhook",
"scopes": ["project.created", "photo.created"]
}
Available scopes:
project.createdproject.updatedproject.deletedphoto.createdphoto.updatedphoto.deleteddocument.createdlabel.createdlabel.deleted
Get Webhook
GET /companycam/v2/webhooks/{id}
Update Webhook
PUT /companycam/v2/webhooks/{id}
Content-Type: application/json
{
"url": "https://example.com/new-webhook",
"enabled": true
}
Delete Webhook
DELETE /companycam/v2/webhooks/{id}
Pagination
CompanyCam uses page-based pagination:
GET /companycam/v2/projects?page=2&per_page=25
Query parameters:
page- Page number (default: 1)per_page- Results per page (default: 25)
Code Examples
JavaScript - List Projects
const response = await fetch(
'https://gateway.maton.ai/companycam/v2/projects?per_page=10',
{
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`
}
}
);
const projects = await response.json();
console.log(projects);
Python - List Projects
import os
import requests
response = requests.get(
'https://gateway.maton.ai/companycam/v2/projects',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'},
params={'per_page': 10}
)
projects = response.json()
for project in projects:
print(f"{project['name']}: {project['id']}")
Python - Create Project with Photo
import os
import requests
headers = {'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
base_url = 'https://gateway.maton.ai/companycam/v2'
# Create project
project_response = requests.post(
f'{base_url}/projects',
headers=headers,
json={
'name': 'Kitchen Renovation',
'address': {
'street_address_1': '456 Oak Ave',
'city': 'Denver',
'state': 'CO',
'postal_code': '80202',
'country': 'US'
}
}
)
project = project_response.json()
print(f"Created project: {project['id']}")
# Add photo to project
photo_response = requests.post(
f'{base_url}/projects/{project["id"]}/photos',
headers=headers,
json={
'uri': 'https://example.com/kitchen-before.jpg',
'tags': ['before', 'kitchen']
}
)
photo = photo_response.json()
print(f"Added photo: {photo['id']}")
Notes
- Project IDs and other IDs are returned as strings
- Timestamps are Unix timestamps (seconds since epoch)
- Photos can be added via URL (uri parameter)
- Comments must be wrapped in a
commentobject - Webhooks use
scopesparameter (notevents) - User roles:
admin,standard,limited - IMPORTANT: When using curl commands, use
curl -gwhen URLs contain brackets to disable glob parsing - IMPORTANT: When piping curl output to
jq, environment variables may not expand correctly. Use Python examples instead.
Rate Limits
| Operation | Limit |
|---|---|
| GET requests | 240 per minute |
| POST/PUT/DELETE | 100 per minute |
When rate limited, the API returns a 429 status code. Implement exponential backoff for retries.
Error Handling
| Status | Meaning |
|---|---|
| 400 | Bad request or missing CompanyCam connection |
| 401 | Invalid or missing Maton API key |
| 404 | Resource not found |
| 422 | Validation error (check error messages) |
| 429 | Rate limited |
| 4xx/5xx | Passthrough error from CompanyCam API |
Troubleshooting: API Key Issues
- Check that the
MATON_API_KEYenvironment variable is set:
echo $MATON_API_KEY
- Verify the API key is valid by listing connections:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.maton.ai/connections')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Troubleshooting: Invalid App Name
- Ensure your URL path starts with
companycam. For example:
- Correct:
https://gateway.maton.ai/companycam/v2/projects - Incorrect:
https://gateway.maton.ai/v2/projects