Quickstart | FieldCamp API
Go from zero to a scheduled job in the FieldCamp API in five steps.
This guide walks you from a fresh API key to a scheduled job visible in the FieldCamp UI. Each step is reversible: you can delete the client and cancel the job once you're done experimenting.
Before you start
- An API key (see Authentication).
- Node.js 20+ or Python 3.10+ if you want to run the end-to-end example at the bottom.
Set your key as an environment variable so the snippets below pick it up:
export FIELDCAMP_API_KEY="<paste your JWT>"Set up your client
Each code tab below assumes one of these clients is already initialized. Copy the relevant block once at the top of your file — every step that follows reuses it.
cURL
# No client setup — each cURL example is self-contained.
# $FIELDCAMP_API_KEY is the exported env var.Node.js
import axios from 'axios';
import FormData from 'form-data';
const fc = axios.create({
baseURL: 'https://api.fieldcamp.ai',
headers: {
'X-API-Key': process.env.FIELDCAMP_API_KEY,
'Authorization': `Bearer ${process.env.FIELDCAMP_API_KEY}`,
'Content-Type': 'application/json',
},
timeout: 30_000,
});Python
import os, json, requests
BASE = "https://api.fieldcamp.ai"
key = os.environ["FIELDCAMP_API_KEY"]
HDRS = {
"X-API-Key": key,
"Authorization": f"Bearer {key}",
"Content-Type": "application/json",
}Step 1 — Confirm your key works
cURL
curl https://api.fieldcamp.ai/api/company-info \
-H "X-API-Key: $FIELDCAMP_API_KEY" \
-H "Authorization: Bearer $FIELDCAMP_API_KEY"Node.js
const { data } = await fc.get('/api/company-info');
console.log(data.data.company.companyName);Python
r = requests.get(f"{BASE}/api/company-info", headers=HDRS).json()
print(r["data"]["company"]["companyName"])Expected: a 200 with your company name in the payload. If not, see
Authentication.
Step 2 — Create (or find) a client
A job needs a client. If your booking system already has a unique identifier per customer, we recommend looking up by email before creating — list endpoints don't support server-side filtering, so you fetch and filter client-side.
cURL
curl https://api.fieldcamp.ai/api/clients \
-H "X-API-Key: $FIELDCAMP_API_KEY" \
-H "Authorization: Bearer $FIELDCAMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jane",
"lastName": "Cooper",
"email": "jane@example.com",
"phoneNumber": { "countryCode": "+1", "number": "5550101234", "countryIdentifier": "us" },
"companyName": "Acme Moving Co.",
"propertyAddress": { "street": "500 Example Ave", "city": "Springfield", "state": "IL", "country": "United States", "zipCode": "62701" },
"billingAddress": { "street": "500 Example Ave", "city": "Springfield", "state": "IL", "country": "United States", "zipCode": "62701" },
"stage": "Active Client",
"properties": [],
"jobFormIds": []
}'Node.js
const { data: clientResp } = await fc.post('/api/clients', {
firstName: 'Jane',
lastName: 'Cooper',
email: 'jane@example.com',
phoneNumber: { countryCode: '+1', number: '5550101234', countryIdentifier: 'us' },
companyName: 'Acme Moving Co.',
propertyAddress: { street: '500 Example Ave', city: 'Springfield', state: 'IL', country: 'United States', zipCode: '62701' },
billingAddress: { street: '500 Example Ave', city: 'Springfield', state: 'IL', country: 'United States', zipCode: '62701' },
stage: 'Active Client',
properties: [],
jobFormIds: [],
});
const clientId = clientResp.data.client.id ?? clientResp.data.client.recordId;Python
resp = requests.post(f"{BASE}/api/clients", headers=HDRS, json={
"firstName": "Jane", "lastName": "Cooper",
"email": "jane@example.com",
"phoneNumber": {"countryCode": "+1", "number": "5550101234", "countryIdentifier": "us"},
"companyName": "Acme Moving Co.",
"propertyAddress": {"street": "500 Example Ave", "city": "Springfield", "state": "IL", "country": "United States", "zipCode": "62701"},
"billingAddress": {"street": "500 Example Ave", "city": "Springfield", "state": "IL", "country": "United States", "zipCode": "62701"},
"stage": "Active Client", "properties": [], "jobFormIds": [],
}).json()
client_id = resp["data"]["client"].get("id") or resp["data"]["client"]["recordId"]Save the returned id (or recordId as a fallback — see the ID fields
quirk).
Step 3 — Create an item
Items are the billable products or services that appear as line items on a job. Create each item once and reuse its ID on every job.
cURL
curl https://api.fieldcamp.ai/api/product-service \
-H "X-API-Key: $FIELDCAMP_API_KEY" \
-H "Authorization: Bearer $FIELDCAMP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Standard 3-person moving crew",
"description": "Up to 4 hours of on-site moving labor with 3 crew",
"price": 299.00,
"type": "Service",
"isActive": true
}'Node.js
const { data: itemResp } = await fc.post('/api/product-service', {
name: 'Standard 3-person moving crew',
description: 'Up to 4 hours of on-site moving labor with 3 crew',
price: 299.0,
type: 'Service',
isActive: true,
});
const itemId = itemResp.data.item.id ?? itemResp.data.item.recordId;Python
resp = requests.post(f"{BASE}/api/product-service", headers=HDRS, json={
"name": "Standard 3-person moving crew",
"description": "Up to 4 hours of on-site moving labor with 3 crew",
"price": 299.00, "type": "Service", "isActive": True,
}).json()
item_id = resp["data"]["item"].get("id") or resp["data"]["item"]["recordId"]Step 4 — Find a technician
A job's assignedToTeams field takes user IDs from GET /api/team,
even though the field name says "teams." Grab one:
cURL
curl https://api.fieldcamp.ai/api/team \
-H "X-API-Key: $FIELDCAMP_API_KEY" \
-H "Authorization: Bearer $FIELDCAMP_API_KEY"Node.js
const { data: teamResp } = await fc.get('/api/team');
const technicianId = teamResp.data.team[0].id;Python
resp = requests.get(f"{BASE}/api/team", headers=HDRS).json()
technician_id = resp["data"]["team"][0]["id"]Step 5 — Create the job
This is the one request with a quirk — POST /api/jobs uses
multipart/form-data, not JSON. Two form fields:
jobData— a JSON-stringified payloadnotes— a plain string
This is the most common mistake integrators make. If you accidentally
POST JSON here, you'll get a 400 with a confusing message. Use the
multipart examples below.
cURL
JOB_DATA='{
"clientId": "'$CLIENT_ID'",
"jobNumber": "BOOKING-5512-MOVE",
"isBillingDifferent": false, "billToClientId": null, "billToClientDetails": null,
"jobAddress": { "street": "500 Example Ave", "city": "Springfield", "state": "IL", "country": "United States", "zipCode": "62701" },
"jobPhone": { "countryCode": "+1", "number": "5550101234", "countryIdentifier": "us" },
"assignedToTeams": ["'$TECH_ID'"],
"jobType": "one-off",
"startDateTime": "2026-05-01T09:00:00.000Z",
"endDateTime": "2026-05-01T17:00:00.000Z",
"scheduleLater": false, "anyTime": false, "serviceDuration": null,
"jobFormIds": [],
"subTotal": 299.00, "tax": 0.00, "total": 299.00,
"discount": 0, "discountType": 1,
"jobStatus": "scheduled",
"jobItems": [{
"itemId": "'$ITEM_ID'", "id": "'$ITEM_ID'",
"itemName": "Standard 3-person moving crew", "description": "",
"taxIds": [], "exemptFromTax": false,
"quantity": 1, "price": 299.00, "total": 299.00, "taxAmount": 0.00,
"orderIndex": 1, "type": "Service", "taxes": [], "action": 1
}],
"timezone": "America/Chicago",
"properties": [],
"isStartTime": true, "isEndTime": true,
"skillsId": [], "priority": "medium",
"notesLinking": { "linkToEstimate": false, "linkToInvoice": false },
"jobLogsTemplateId": null, "templateId": null, "templateSnapshot": null,
"visits": []
}'
curl https://api.fieldcamp.ai/api/jobs \
-H "X-API-Key: $FIELDCAMP_API_KEY" \
-H "Authorization: Bearer $FIELDCAMP_API_KEY" \
-F "jobData=$JOB_DATA" \
-F "notes=Booking 5512. Stairs: yes. Piano: no."Node.js
import FormData from 'form-data';
const jobData = {
clientId,
jobNumber: 'BOOKING-5512-MOVE',
isBillingDifferent: false, billToClientId: null, billToClientDetails: null,
jobAddress: { street: '500 Example Ave', city: 'Springfield', state: 'IL', country: 'United States', zipCode: '62701' },
jobPhone: { countryCode: '+1', number: '5550101234', countryIdentifier: 'us' },
assignedToTeams: [technicianId],
jobType: 'one-off',
startDateTime: '2026-05-01T09:00:00.000Z',
endDateTime: '2026-05-01T17:00:00.000Z',
scheduleLater: false, anyTime: false, serviceDuration: null,
jobFormIds: [],
subTotal: 299.0, tax: 0, total: 299.0,
discount: 0, discountType: 1,
jobStatus: 'scheduled',
jobItems: [{
itemId, id: itemId,
itemName: 'Standard 3-person moving crew', description: '',
taxIds: [], exemptFromTax: false,
quantity: 1, price: 299.0, total: 299.0, taxAmount: 0,
orderIndex: 1, type: 'Service', taxes: [], action: 1,
}],
timezone: 'America/Chicago',
properties: [],
isStartTime: true, isEndTime: true,
skillsId: [], priority: 'medium',
notesLinking: { linkToEstimate: false, linkToInvoice: false },
jobLogsTemplateId: null, templateId: null, templateSnapshot: null,
visits: [],
};
const form = new FormData();
form.append('jobData', JSON.stringify(jobData));
form.append('notes', 'Booking 5512. Stairs: yes. Piano: no.');
const { data: jobResp } = await fc.post('/api/jobs', form, { headers: form.getHeaders() });
console.log('Created job:', jobResp.data.jobId ?? jobResp.data.job.id);Python
import json
job_data = {
"clientId": client_id,
"jobNumber": "BOOKING-5512-MOVE",
"isBillingDifferent": False, "billToClientId": None, "billToClientDetails": None,
"jobAddress": {"street": "500 Example Ave", "city": "Springfield", "state": "IL", "country": "United States", "zipCode": "62701"},
"jobPhone": {"countryCode": "+1", "number": "5550101234", "countryIdentifier": "us"},
"assignedToTeams": [technician_id],
"jobType": "one-off",
"startDateTime": "2026-05-01T09:00:00.000Z",
"endDateTime": "2026-05-01T17:00:00.000Z",
"scheduleLater": False, "anyTime": False, "serviceDuration": None,
"jobFormIds": [],
"subTotal": 299.0, "tax": 0.0, "total": 299.0,
"discount": 0, "discountType": 1,
"jobStatus": "scheduled",
"jobItems": [{
"itemId": item_id, "id": item_id,
"itemName": "Standard 3-person moving crew", "description": "",
"taxIds": [], "exemptFromTax": False,
"quantity": 1, "price": 299.0, "total": 299.0, "taxAmount": 0.0,
"orderIndex": 1, "type": "Service", "taxes": [], "action": 1,
}],
"timezone": "America/Chicago",
"properties": [],
"isStartTime": True, "isEndTime": True,
"skillsId": [], "priority": "medium",
"notesLinking": {"linkToEstimate": False, "linkToInvoice": False},
"jobLogsTemplateId": None, "templateId": None, "templateSnapshot": None,
"visits": [],
}
resp = requests.post(f"{BASE}/api/jobs",
headers={"X-API-Key": key, "Authorization": f"Bearer {key}"},
files={"jobData": (None, json.dumps(job_data)), "notes": (None, "Booking 5512.")},
).json()
print("Created job:", resp["data"].get("jobId") or resp["data"]["job"]["id"])A visit is auto-generated from startDateTime/endDateTime. Refresh the
FieldCamp UI — the job is on the calendar.
Verify
GET /api/jobs and filter client-side by jobNumber:
curl https://api.fieldcamp.ai/api/jobs \
-H "X-API-Key: $FIELDCAMP_API_KEY" \
-H "Authorization: Bearer $FIELDCAMP_API_KEY" \
| jq '.data.jobs[] | select(.jobNumber == "BOOKING-5512-MOVE")'Full end-to-end Node.js example
Copy this into a single file (create-job.js), run with node create-job.js:
import axios from 'axios';
import FormData from 'form-data';
const KEY = process.env.FIELDCAMP_API_KEY;
if (!KEY) throw new Error('Set FIELDCAMP_API_KEY');
const fc = axios.create({
baseURL: 'https://api.fieldcamp.ai',
headers: {
'X-API-Key': KEY,
'Authorization': `Bearer ${KEY}`,
'Content-Type': 'application/json',
},
timeout: 30_000,
});
async function run() {
const { data: client } = await fc.post('/api/clients', {
firstName: 'Jane', lastName: 'Cooper',
email: `jane+${Date.now()}@example.com`,
phoneNumber: { countryCode: '+1', number: '5550101234', countryIdentifier: 'us' },
companyName: 'Acme Moving Co.',
propertyAddress: { street: '500 Example Ave', city: 'Springfield', state: 'IL', country: 'United States', zipCode: '62701' },
billingAddress: { street: '500 Example Ave', city: 'Springfield', state: 'IL', country: 'United States', zipCode: '62701' },
stage: 'Active Client', properties: [], jobFormIds: [],
});
const clientId = client.data.client.id ?? client.data.client.recordId;
const { data: item } = await fc.post('/api/product-service', {
name: 'Standard 3-person moving crew',
description: 'Up to 4 hours of on-site moving labor with 3 crew',
price: 299.0, type: 'Service', isActive: true,
});
const itemId = item.data.item.id ?? item.data.item.recordId;
const { data: team } = await fc.get('/api/team');
const technicianId = team.data.team[0].id;
const jobData = {
clientId, jobNumber: `DEMO-${Date.now()}`,
isBillingDifferent: false, billToClientId: null, billToClientDetails: null,
jobAddress: { street: '500 Example Ave', city: 'Springfield', state: 'IL', country: 'United States', zipCode: '62701' },
jobPhone: { countryCode: '+1', number: '5550101234', countryIdentifier: 'us' },
assignedToTeams: [technicianId], jobType: 'one-off',
startDateTime: '2026-05-01T09:00:00.000Z', endDateTime: '2026-05-01T17:00:00.000Z',
scheduleLater: false, anyTime: false, serviceDuration: null, jobFormIds: [],
subTotal: 299.0, tax: 0, total: 299.0, discount: 0, discountType: 1,
jobStatus: 'scheduled',
jobItems: [{
itemId, id: itemId, itemName: 'Standard 3-person moving crew',
description: '', taxIds: [], exemptFromTax: false,
quantity: 1, price: 299.0, total: 299.0, taxAmount: 0,
orderIndex: 1, type: 'Service', taxes: [], action: 1,
}],
timezone: 'America/Chicago', properties: [],
isStartTime: true, isEndTime: true, skillsId: [], priority: 'medium',
notesLinking: { linkToEstimate: false, linkToInvoice: false },
jobLogsTemplateId: null, templateId: null, templateSnapshot: null,
visits: [],
};
const form = new FormData();
form.append('jobData', JSON.stringify(jobData));
form.append('notes', 'Demo job created via Quickstart.');
const { data: jobResp } = await fc.post('/api/jobs', form, { headers: form.getHeaders() });
console.log('Created job:', jobResp.data.jobId ?? jobResp.data.job.id);
}
run().catch(err => { console.error(err.response?.data ?? err); process.exit(1); });