1. Getting an API key
Every request must carry an API key in the X-Api-Key header. Keys are issued by the
API administrator and are bound to a set of scopes:
| Scope | What it allows |
|---|---|
read | GET on every resource and view. |
write | Everything read allows, plus POST, PUT and DELETE. |
2. Your first request
curl -H "X-Api-Key: YOUR_KEY" \
"https://localhost:7262/api/v1/customers?page=1&pageSize=25"
Every collection response has the same envelope:
{
"items": [ { "id": 1, "customerNo": "1001", "customerName": "Acme Ltd" } ],
"page": 1,
"pageSize": 25,
"totalItems": 842,
"totalPages": 34,
"hasPrevious": false,
"hasNext": true
}
3. The five operations
Every table resource supports the same five calls. {resource} is one of the paths listed
in section 5.
| Call | Scope | Description |
|---|---|---|
GET /api/v1/{resource} | read | List records, with paging, filtering, search and sorting. |
GET /api/v1/{resource}/{id} | read | Fetch one record by its id. |
POST /api/v1/{resource} | write | Create a record. Returns 201 and the created row (with its new id). |
PUT /api/v1/{resource}/{id} | write | Update a record. The id comes from the URL; an id in the body is ignored. |
DELETE /api/v1/{resource}/{id} | write | Delete a record. Returns 204. |
Resources under /api/v1/views/… are database views and are read-only
(list only).
4. Querying a collection
| Parameter | Meaning |
|---|---|
page | 1-based page number. Default 1. |
pageSize | Rows per page, 1–200. Default 50. |
sort | Column name to sort by, e.g. sort=CustomerName. |
order | asc (default) or desc. |
q | Free-text search across all text columns of the resource. |
| any column name | Exact-match filter, e.g. ?CustomerNo=1001&Active=true. |
GET /api/v1/objects?StructureID=42&sort=SerialNo&order=desc&pageSize=100
GET /api/v1/customers?q=acme
GET /api/v1/views/structure-certificates?CustomerNo=1001
400 Bad Request. Values are always sent to SQL Server
as bound parameters, so filters can never be used for SQL injection.
5. Available resources
Customers
- customers
- customer-contacts
- customer-structures
- customer-structure-contacts
Structures
- structures
- structure-types
Objects
- objects
- object-brands
- object-photos
- object-types
- object-type-groups
- object-size-types
- object-guids
Certificates
- certificates
- certificate-lines
- certificate-orders
- certificate-types
- certificate-service-statuses
- cp-certificate-lines
- cp-certificate-line-templates
Inspections
- inspection-types
- works
- reports
Service personnel & sales
- service-personnel
- service-personnel-certificates
- sales
Reference data
- zips
- settings
Views (read-only)
- 36 reporting views under
/api/v1/views/…
The API explorer lists every resource with its exact column names and types.
6. Creating and updating
curl -X POST "https://localhost:7262/api/v1/customers" \
-H "X-Api-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "customerNo": "1099", "customerName": "New Customer" }'
Only the resource's own columns are accepted. Related collections are never read from the body, and the primary key is always assigned by the database on create and taken from the URL on update.
7. Errors
Failures use RFC 7807 problem+json.
| Status | Meaning |
|---|---|
400 | Unknown filter/sort column, bad value, or invalid paging. |
401 | Missing or invalid X-Api-Key. |
403 | Key is valid but lacks the required scope (usually write). |
404 | No record with that id. |
409 | The change violates a database constraint (duplicate key, missing parent row…). |
429 | Rate limit exceeded — slow down and retry. |
500 | Unexpected server error. Details are logged server-side only. |
8. Limits and operational notes
- Rate limit: 300 requests per minute per API key (configurable).
- Maximum page size: 200 rows.
- HTTPS only — plain HTTP requests are redirected, and HSTS is enabled outside development.
- Browser apps must be added to the CORS allow-list before they can call the API.
GET /healthreports service and database availability (no key required).
9. Example client (JavaScript)
const BASE = "https://localhost:7262/api/v1";
const KEY = process.env.FX_API_KEY; // keep this server-side
async function fx(path, init = {}) {
const res = await fetch(`${BASE}/${path}`, {
...init,
headers: { "X-Api-Key": KEY, "Content-Type": "application/json", ...init.headers },
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
return res.status === 204 ? null : res.json();
}
const page = await fx("customers?pageSize=25&sort=CustomerName");
const made = await fx("customers", {
method: "POST",
body: JSON.stringify({ customerNo: "1099", customerName: "New Customer" }),
});
await fx(`customers/${made.id}`, { method: "DELETE" });