ApptGuard API

Who this is for: Business owners who want to connect their ApptGuard account to their own website, app, or software. You do not need to be a professional developer to use this API — this guide walks through everything step by step.

The ApptGuard API lets your external software read and write data in your account. Every action available through the API is something that already exists in your dashboard — the API simply gives your other tools a way to do the same things automatically.

For example, if your website lets customers book appointments, it can use this API to create those appointments directly in your ApptGuard account instead of someone having to enter them manually.

What You Can Do

The API gives you access to the following parts of your account:

  • Appointments — read, create, update, and delete appointments
  • Clients — read and create client records, update contact information and tags
  • Inventory — read stock levels, add or remove stock, set exact quantities
  • Quotes — read quotes and their status
  • Invoices — read invoices
  • Reports — pull appointment stats and weekly product usage breakdowns
  • Expenses ★ Elite — create, read, update, and delete business expenses with tax categories and receipt tracking
  • Mileage Log ★ Elite — log trips with start/end addresses, times, and mileage
  • P&L Summary ★ Elite — pull profit & loss data for any date range, including revenue by client/service and expense breakdown by category
  • Offline Pack ★ Elite — download a day's appointments and forms for offline use

The API only exposes your own account's data. It cannot access other businesses' accounts, and it cannot change how the platform itself works.

★ Elite plan endpoints are available exclusively to accounts on the Elite plan with an active subscription. Calling them on any other plan returns a 403 Forbidden with the message "This feature requires the Elite plan."

Get Your API Key

An API key is a long string of characters that proves who you are when your software contacts ApptGuard. Think of it like a password that your application uses instead of you.

  1. Sign in to your ApptGuard account.
  2. Go to Settings and click the API tab.
  3. Click Generate New Key and give it a name (for example, "My Website" or "Zapier").
  4. Copy the key immediately — it is only shown once. Store it somewhere safe like a password manager or your server's environment variables.
Important: Your API key is shown only once when it is created. If you lose it, you will need to revoke it and generate a new one. Never paste your API key into public code, GitHub, or a website's front-end JavaScript.

You can create up to 5 active API keys per account. This lets you give separate keys to separate applications, and revoke one without breaking the others.

Authentication

Every request to the API must include your API key in a special header called X-API-Key.

curl https://appt-guard.nihnyx.com/api/v1/ping \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE"

If your key is valid, the response will be:

{
  "ok": true,
  "account": "Your Business Name",
  "plan": "elite",
  "timestamp": "2026-07-01T12:00:00.000Z"
}

The plan field shows which plan your account is on. Elite-only endpoints are only accessible when this value is "elite".

If the key is wrong or has been revoked, you will receive:

{
  "error": "Invalid or revoked API key."
}

Base URL

All API endpoints begin with the same base URL:

https://appt-guard.nihnyx.com/api/v1

Elite endpoints follow the same pattern but under the /elite/ path:

https://appt-guard.nihnyx.com/api/elite

Rate Limits

Rate limits scale with your account's plan — higher tiers get more requests per minute, in addition to unlocking more endpoints (see Plan Access below). You can check your current limit at any time via GET /api/apikeys/limits from your dashboard session, or by reading the X-RateLimit info returned on a 429 response.

PlanRequests / minute
Free20
Starter60
Growth120
Pro300
Elite600

If you exceed your plan's limit, the API responds with HTTP 429:

{
  "error": "Rate limit exceeded. Your plan allows 60 requests per minute.",
  "yourPlan": "starter",
  "limit": 60,
  "upgrade": "https://appt-guard.nihnyx.com/pricing/"
}

Rate limits are enforced per API key, independently of which endpoints your plan can access — this applies even to endpoints your plan is allowed to call, so raising your request volume alone can't substitute for a higher plan's endpoint access.

Plan Access

Each endpoint requires a minimum plan tier. Calling an endpoint above your plan returns 403 Forbidden with the plan you'd need — this is enforced server-side on every request, not just hidden in the dashboard UI, so it can't be bypassed by calling the API directly.

PlanAdds access to
Free/ping, /account only
Starter+ Appointments, Clients, Quotes
Growth+ Inventory, Appointment reports, Online payment status
Pro+ Invoices, Weekly product reports
Elite+ Expenses, Mileage, P&L reports (via /api/elite/*)
{
  "error": "This endpoint requires the Growth ($83/mo) plan or above.",
  "yourPlan": "starter",
  "upgrade": "https://appt-guard.nihnyx.com/pricing/"
}

Understanding Errors

StatusMeaningWhat to do
200SuccessRead the response body
201CreatedRead the response for the new item
400Bad Request — something in your request is wrongRead the error field for details
401Unauthorized — API key is missing or invalidCheck your X-API-Key header
403Forbidden — your plan does not include this endpointSee Plan Access for the tier you need
404Not Found — the record does not existCheck the ID or phone number you are using
409Conflict — record already existsUse PUT to update instead of POST to create
429Too Many Requests — rate limit exceededWait 60 seconds, or upgrade for a higher limit
500Server ErrorTry again; contact support if it persists

Tutorial: Your First Request

curl https://appt-guard.nihnyx.com/api/v1/ping \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE"

You should see a JSON response with your account name, plan, and the current time. The plan field will show "elite" if your account has access to the Elite endpoints.

Tutorial: Managing Appointments

List upcoming appointments

curl "https://appt-guard.nihnyx.com/api/v1/appointments?from=2026-07-01&status=confirmed" \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE"
{
  "count": 2,
  "data": [
    {
      "id": "abc123",
      "clientName": "Jane Smith",
      "phone": "+15551234567",
      "date": "2026-07-15",
      "time": "10:00",
      "service": "HVAC Inspection",
      "status": "confirmed"
    }
  ]
}

Create an appointment

curl -X POST https://appt-guard.nihnyx.com/api/v1/appointments \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"clientName":"Jane Smith","phone":"+15551234567","date":"2026-07-15","time":"10:00","service":"HVAC Inspection"}'

Update an appointment

curl -X PUT https://appt-guard.nihnyx.com/api/v1/appointments/abc123 \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"status":"cancelled","notes":"Client called to cancel"}'

Delete an appointment

curl -X DELETE https://appt-guard.nihnyx.com/api/v1/appointments/abc123 \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE"

Tutorial: Working with Clients

Look up a client by phone

curl "https://appt-guard.nihnyx.com/api/v1/clients/%2B15551234567" \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE"
URL encoding: Replace the + in phone numbers with %2B when placing them in a URL path.

Search clients

curl "https://appt-guard.nihnyx.com/api/v1/clients?search=Smith" \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE"

Create a client

curl -X POST https://appt-guard.nihnyx.com/api/v1/clients \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"name":"Jane Smith","phone":"+15551234567","email":"[email protected]","tags":["Residential","VIP"]}'

Tutorial: Tracking Inventory

Get inventory list

curl https://appt-guard.nihnyx.com/api/v1/inventory \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE"

Adjust stock after a job

curl -X POST https://appt-guard.nihnyx.com/api/v1/inventory/ITEM_ID/adjust \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"type":"remove","qty":1,"note":"Used on Job #47"}'

Adjustment types: add (new stock arrived), remove (used on a job), set (exact count after a physical count).

Check for low stock

curl "https://appt-guard.nihnyx.com/api/v1/inventory?low_stock=true" \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE"

★ Tutorial: P&L & Mileage (Elite)

Elite plan required. The endpoints in this section return 403 Forbidden on Starter, Growth, and Pro plans. You can check your plan at any time by calling GET /api/v1/ping and reading the plan field.

Log an expense

Use this after purchasing supplies, paying a vendor, or recording any other business cost. The category field maps to IRS Schedule C tax categories so your records are already organized at tax time.

curl -X POST https://appt-guard.nihnyx.com/api/elite/expenses \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Engine oil and filter",
    "amount": 48.50,
    "date": "2026-07-01",
    "category": "Vehicle & Mileage",
    "notes": "Truck #2 oil change before busy season"
  }'

Valid category values:

  • Advertising
  • Insurance
  • Legal & Professional
  • Materials & Supplies
  • Meals & Entertainment
  • Office Supplies
  • Rent
  • Repairs & Maintenance
  • Travel
  • Utilities
  • Vehicle & Mileage
  • Wages & Contract Labor
  • Other

Log a trip

Record a business trip with start and end address, optional times, and miles. If you do not know the exact mileage, leave miles out — you can update it later.

curl -X POST https://appt-guard.nihnyx.com/api/elite/mileage \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "date": "2026-07-01",
    "startAddress": "123 Main St, Memphis, TN",
    "endAddress": "456 Oak Ave, Memphis, TN",
    "startTime": "08:30",
    "endTime": "08:52",
    "miles": 7.4,
    "purpose": "Client visit — annual pond cleaning"
  }'

Pull a P&L summary

Get a complete profit and loss breakdown for any date range. Revenue comes from paid invoices. Expenses come from your logged expense records. The response includes percentage breakdowns by client, service, and expense category.

curl "https://appt-guard.nihnyx.com/api/elite/pnl?from=2026-07-01&to=2026-07-31" \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE"
Example response
{
  "period": { "from": "2026-07-01", "to": "2026-07-31" },
  "revenue": 8420.00,
  "expenses": 1240.50,
  "netProfit": 7179.50,
  "invoiceCount": 23,
  "expenseCount": 7,
  "revenueByClient": [
    { "name": "Greenfield HOA",  "amount": 3960.00, "pct": 47.0 },
    { "name": "Jane Smith",       "amount": 840.00,  "pct": 10.0 }
  ],
  "revenueByService": [
    { "name": "Pond Cleaning",    "amount": 3960.00, "pct": 47.0 },
    { "name": "HVAC Inspection", "amount": 2520.00, "pct": 30.0 }
  ],
  "expensesByCategory": [
    { "name": "Vehicle & Mileage", "amount": 372.15, "pct": 30.0 },
    { "name": "Materials & Supplies", "amount": 248.10, "pct": 20.0 }
  ]
}

Download an offline pack

Use this to grab everything you need for a day of fieldwork — all appointments and form templates — so you can keep working without cell service.

curl "https://appt-guard.nihnyx.com/api/elite/offline-pack?date=2026-07-04" \
  -H "X-API-Key: ag_live_YOUR_KEY_HERE" \
  -o offline-2026-07-04.json

The -o flag saves the response directly to a file. The pack contains all appointments for that date, all your form templates, and any submissions already collected.

Tutorial: Using JavaScript (Node.js)

Never use your API key in browser JavaScript. API calls that include your key must only be made from your server.

Basic setup

// Store in .env — never commit to Git
const API_KEY  = process.env.APPTGUARD_API_KEY;
const BASE_V1  = 'https://appt-guard.nihnyx.com/api/v1';
const BASE_EL  = 'https://appt-guard.nihnyx.com/api/elite';

async function ag(path, method = 'GET', body) {
  const res = await fetch(path, {
    method,
    headers: {
      'X-API-Key': API_KEY,
      ...(body ? { 'Content-Type': 'application/json' } : {})
    },
    body: body ? JSON.stringify(body) : undefined
  });
  if (!res.ok) throw new Error((await res.json()).error);
  return res.json();
}

Log an expense (Elite)

const expense = await ag(`${BASE_EL}/expenses`, 'POST', {
  description: 'Engine oil and filter',
  amount:      48.50,
  date:        '2026-07-01',
  category:    'Vehicle & Mileage',
});
console.log('Logged expense:', expense.id);

Pull this month's P&L (Elite)

const now   = new Date();
const from  = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-01`;
const today = now.toISOString().slice(0,10);

const pnl = await ag(`${BASE_EL}/pnl?from=${from}&to=${today}`);
console.log(`Revenue: $${pnl.revenue} | Expenses: $${pnl.expenses} | Net: $${pnl.netProfit}`);

// Show top revenue source
const top = pnl.revenueByService[0];
if (top) console.log(`Top service: ${top.name} (${top.pct}% of revenue)`);

Log a trip and list all mileage (Elite)

// Log a new trip
await ag(`${BASE_EL}/mileage`, 'POST', {
  date:         '2026-07-01',
  startAddress: '123 Main St, Memphis, TN',
  endAddress:   '456 Oak Ave, Memphis, TN',
  startTime:    '08:30',
  endTime:      '08:52',
  miles:        7.4,
  purpose:      'Client visit',
});

// List all trips
const trips = await ag(`${BASE_EL}/mileage`);
const totalMiles = trips.reduce((s, t) => s + (t.miles || 0), 0);
console.log(`Total miles logged: ${totalMiles.toFixed(1)}`);

Tutorial: Using Python

import requests, os

API_KEY  = os.environ.get('APPTGUARD_API_KEY')
BASE_V1  = 'https://appt-guard.nihnyx.com/api/v1'
BASE_EL  = 'https://appt-guard.nihnyx.com/api/elite'
HEADERS  = {'X-API-Key': API_KEY}

Log an expense (Elite)

r = requests.post(f'{BASE_EL}/expenses', headers=HEADERS, json={
    'description': 'Engine oil and filter',
    'amount':      48.50,
    'date':        '2026-07-01',
    'category':    'Vehicle & Mileage',
})
r.raise_for_status()
print('Created expense:', r.json()['id'])

Pull a P&L summary (Elite)

r = requests.get(f'{BASE_EL}/pnl', headers=HEADERS,
    params={'from': '2026-07-01', 'to': '2026-07-31'})
r.raise_for_status()
pnl = r.json()

print(f"Revenue:  ${pnl['revenue']:,.2f}")
print(f"Expenses: ${pnl['expenses']:,.2f}")
print(f"Net:      ${pnl['netProfit']:,.2f}")

for row in pnl['revenueByService']:
    print(f"  {row['name']}: {row['pct']}% of revenue")

Reference: Meta

MethodEndpointDescription
GET/api/v1/pingVerify your API key. Returns account name, plan, and server time.
GET/api/v1/accountReturns your account and business details including current plan.

Reference: Appointments

GET /api/v1/appointments

ParameterTypeDescription
statusstringoptionalconfirmed, pending, cancelled
fromstringoptionalStart date. Format: YYYY-MM-DD
tostringoptionalEnd date. Format: YYYY-MM-DD
phonestringoptionalFilter by client phone
limitnumberoptionalDefault: 100. Max: 500.

GET /api/v1/appointments/:id

Returns a single appointment by ID.

POST /api/v1/appointments

FieldTypeDescription
clientNamestringrequiredFull name of the client
phonestringrequiredE.164 format (e.g. +15551234567)
datestringrequiredFormat: YYYY-MM-DD
timestringoptional24-hour format. Default: 09:00
servicestringoptionalService name
notesstringoptionalInternal notes
statusstringoptionalDefault: confirmed

PUT /api/v1/appointments/:id

Update any fields on an existing appointment. Only include what you want to change.

DELETE /api/v1/appointments/:id

Permanently deletes an appointment.

Reference: Clients

GET /api/v1/clients

ParameterTypeDescription
searchstringoptionalSearch by name, phone, or email
tagstringoptionalFilter by tag (exact match)
limitnumberoptionalDefault: 100. Max: 500.

GET /api/v1/clients/:phone

Returns a single client by phone. URL-encode + as %2B.

POST /api/v1/clients

FieldTypeDescription
namestringrequiredClient's full name or company name
phonestringoptionalE.164 format
emailstringoptionalEmail address
tagsarrayoptionalE.g. ["Commercial","VIP"]
addressstringoptionalStreet address
notesstringoptionalA note to add to the client's record

PUT /api/v1/clients/:phone

Allowed fields: name, email, address, tags. Tags are merged, not replaced.

Reference: Inventory

GET /api/v1/inventory

ParameterTypeDescription
categorystringoptionalFilter by category name
low_stockbooleanoptionaltrue returns only items at or below threshold

POST /api/v1/inventory/:id/adjust

FieldTypeDescription
typestringrequiredadd, remove, or set
qtynumberrequiredQuantity to add, remove, or set to
notestringoptionalReason (stored in stock history)

Reference: Quotes

GET /api/v1/quotes

ParameterTypeDescription
statusstringoptionaldraft, sent, approved, changes_requested
limitnumberoptionalDefault: 50. Max: 200.

GET /api/v1/quotes/:id

Returns the full quote including line items, tax rates, and totals.

Reference: Invoices

GET /api/v1/invoices

ParameterTypeDescription
statusstringoptionaloutstanding or paid. Note: there's no separate "overdue" status — an outstanding invoice is overdue once dueDate has passed.
limitnumberoptionalDefault: 50. Max: 200.
Example response
{
  "count": 1,
  "data": [
    {
      "id": "a1b2c3d4-...",
      "invoiceNumber": 1042,
      "clientName": "Jane Doe",
      "clientPhone": "+1 555 000 0000",
      "clientEmail": "[email protected]",
      "items": [{ "description": "Cleaning", "qty": 1, "price": 100 }],
      "amount": 100,
      "dueDate": "2026-08-01",
      "status": "outstanding",
      "paidAt": null,
      "appointmentId": null,
      "signatureUrl": null,
      "viewToken": "9f8e7d...",
      "createdAt": "2026-07-01T12:00:00.000Z"
    }
  ]
}

viewToken powers the client-facing, no-login invoice link at https://appt-guard.nihnyx.com/invoice/?token=<viewToken> — the same link sent automatically by email/text. If the connected business has Stripe Connect set up (Growth+ plan) and the invoice is unpaid, that page shows a live "Pay Now" button; otherwise it shows the business's contact info instead.

Reference: Reports

GET /api/v1/reports/appointments/stats

Returns appointment counts by status and for the current calendar month.

Example response
{
  "total":     142,
  "thisMonth": 18,
  "confirmed": 130,
  "cancelled": 9,
  "pending":   3
}

GET /api/v1/reports/weekly-products

Breakdown of products and services booked in a date range. Requires Growth plan or above.

ParameterTypeDescription
fromstringoptionalStart date. Default: 7 days ago.
tostringoptionalEnd date. Default: today.

★ Reference: Expenses Elite

Base path: https://appt-guard.nihnyx.com/api/elite/expenses
MethodEndpointDescription
GET/api/elite/expensesList all expenses, newest first.
POST/api/elite/expensesCreate a new expense entry.
PUT/api/elite/expenses/:idUpdate an existing expense.
DEL/api/elite/expenses/:idDelete an expense.

POST / PUT fields

FieldTypeDescription
descriptionstringrequiredWhat the expense was for
amountnumberrequiredDollar amount (e.g. 48.50)
datestringrequiredFormat: YYYY-MM-DD
categorystringoptionalIRS Schedule C category. See full list above. Default: Other
notesstringoptionalAdditional detail
receiptUrlstringoptionalURL to a receipt image or PDF

★ Reference: Mileage Log Elite

Base path: https://appt-guard.nihnyx.com/api/elite/mileage
MethodEndpointDescription
GET/api/elite/mileageList all trips, newest first.
POST/api/elite/mileageLog a new trip.
PUT/api/elite/mileage/:idUpdate a trip entry.
DEL/api/elite/mileage/:idDelete a trip entry.

POST / PUT fields

FieldTypeDescription
datestringrequiredFormat: YYYY-MM-DD
startAddressstringrequiredFull starting address
endAddressstringrequiredFull ending address
startTimestringoptionalDeparture time. Format: HH:MM
endTimestringoptionalArrival time. Format: HH:MM
milesnumberoptionalDistance in miles. Can be added later via PUT.
purposestringoptionalReason for the trip (e.g. "Client visit")
notesstringoptionalAdditional notes

★ Reference: P&L Summary Elite

GET /api/elite/pnl

Returns a profit and loss summary for a date range. Revenue is sourced from paid invoices. Expenses come from your logged expense records.

ParameterTypeDescription
fromstringoptionalStart date. Format: YYYY-MM-DD. Default: first day of current month.
tostringoptionalEnd date. Format: YYYY-MM-DD. Default: today.
Response fields
FieldTypeDescription
revenuenumberTotal from paid invoices in the period
expensesnumberTotal logged expenses in the period
netProfitnumberrevenue minus expenses
invoiceCountnumberNumber of paid invoices counted
expenseCountnumberNumber of expense entries counted
revenueByClientarrayEach entry: { name, amount, pct } — sorted largest first
revenueByServicearrayEach entry: { name, amount, pct } — sorted largest first
expensesByCategoryarrayEach entry: { name, amount, pct } — sorted largest first

★ Reference: Offline Pack Elite

GET /api/elite/offline-pack

Returns all appointments, form templates, and existing form submissions for a given date, packaged as a single JSON object. Use this to download the day's workload before entering a location without cell service.

ParameterTypeDescription
datestringoptionalFormat: YYYY-MM-DD. Default: today.
Response fields
FieldTypeDescription
exportedAtstringISO timestamp when the pack was generated
datestringThe date this pack covers
templatesarrayAll your active form templates
appointmentsarrayAll appointments scheduled for that date
submissionsarrayAny form submissions already collected for those appointments

Security Model

  • Account-scoped. A key can only read and write data belonging to the account that generated it.
  • Plan-enforced. Elite endpoints check your active subscription on every request. Downgrading your plan immediately blocks access to those endpoints.
  • No administrative access. Keys cannot change billing, passwords, or platform settings.
  • Audit trail. Every request is logged. View recent activity under Settings → API.
  • Revocable instantly. Revoking a key in Settings takes effect within seconds.

Best Practices

  • Use environment variables. Store your key in a .env file or your hosting platform's secret manager — never in your source code.
  • One key per application. Three tools, three keys. If one is compromised, revoke only that one.
  • Never expose keys in client-side JavaScript. API calls must be made from your server.
  • Check the plan field on /ping. Before making Elite API calls in your code, confirm plan === "elite" so your app can give a clear message if the account has been downgraded.
  • Monitor the audit log. Check API activity in Settings periodically. Unexpected requests mean the key should be revoked immediately.
  • Handle errors gracefully. Always check the HTTP status code. Handle 401, 403, and 429 explicitly so your application degrades cleanly.