FieldCamp

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 payload
  • notes — 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); });

On this page