Skip to main content

Appointments API

πŸš€ Why Use the Appointments API?​

Appointments are the heartbeat of warehouse operations in DataDocks. They represent scheduled time slots at your docks or yards for receiving or shipping inventory. The Appointments API gives you programmatic control over your entire warehouse scheduling workflowβ€”from booking slots to tracking truck movements to completing shipments.

Real Problems This API Solves​

  • Eliminate double-entry: Sync appointments directly between your ERP/WMS and DataDocks
  • Reduce communication overhead: Automate notifications between warehouse staff and carriers
  • Optimize dock utilization: Programmatically schedule appointments to maximize efficiency
  • Track KPIs in real-time: Monitor on-time performance, dwell times, and throughput
  • Enhance visibility: Give carriers and customers self-service access to appointment status

5-Minute Quickstart​

Want to see the API in action right now? Follow these steps to get your first appointment created:

  1. Request and Get your API token from support
  2. Find your location subdomain (e.g., your-warehouse.datadocks.com)
  3. Create a basic appointment with this command:
See quickstart cURL example
curl -X POST \
https://your-warehouse.datadocks.com/api/v1/appointments \
-H 'Authorization: Token YOUR_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"appointment": {
"scheduled_at": "2023-12-15T10:00:00-05:00",
"duration": 60,
"dock_name": "Dock 1",
"shipping_number": "SHIP-12345"
}
}'
  1. A successful request returns 200 OK with your appointment. Check your DataDocks dashboard to see it. Use an appropriate date and an existing dock, and include any extra fields required by your location.

API Architecture Overview​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Your System │──────▢│ DataDocks API │──────▢│ Warehouse Ops β”‚
β”‚ (ERP/WMS/TMS) │◀─────── (REST/JSON) │◀─────── (Scheduling) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β–²
β”‚ β”‚
β–Ό β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Carrier Apps β”‚
β”‚ (Check-in) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Authentication​

All API requests must include your API token in the Authorization header:

Authorization: Token YOUR_API_TOKEN

API tokens belong to users. Location access and appointment permissions follow that user's permissions. Audit logs identify the API user; the display name depends on how the user was configured. Use the full location hostname, including its organization suffix where applicable.

Important Limitations​

  • Recurring appointments cannot be created or modified through the API
  • Appointment deletion is not supported through the API (use cancellation instead)
  • DateTime parameters should include timezone information to avoid ambiguity; values without it use the location's timezone.
  • Only non-recurring appointments are returned in listing endpoints, including generated instances but excluding recurring templates.

Listing Appointments​

Purpose​

Retrieve a paginated list of appointments with optional filtering by date range or purchase order number. Use this endpoint for syncing appointments with your systems or generating reports.

Business Use Cases​

  • Sync appointments with your WMS or TMS for the coming week
  • Generate reports on upcoming shipments by dock or carrier
  • Display appointments in a dashboard with status monitoring
  • Track KPIs like on-time performance, dwell times, and dock utilization

HTTP Request​

GET https://[location_subdomain].datadocks.com/api/v1/appointments

Query Parameters​

ParameterTypeRequiredDescriptionExample
pageIntegerNoPage number for pagination (defaults to 1)page=2
internal_idStringNoFilter by internal appointment ID (case-insensitive exact match)internal_id=ERP-A12345
po_numberStringNoFilter by purchase order number (case-insensitive exact match)po_number=PO-12345
fromStringNoFilter appointments scheduled on or after this date/time (location timezone)from=2023-10-01 08:00 AM
toStringNoFilter appointments scheduled before this date/time (location timezone)to=2023-10-05 06:00 PM

Filters can be combined. Results are ordered by appointment ID. Date filters exclude appointments without a scheduled time. PO filtering checks packing-list PO numbers and linked purchase orders.

Response Format​

The response contains a paginated array of appointment objects, each with the following fields. Unset optional values can be null. Honor the offset in returned ISO8601 timestamps rather than assuming a particular timezone:

FieldTypeDescription
idIntegerUnique identifier for the appointment
location_nameStringName of the warehouse; included in list, show, create, and update responses
locationStringCompatibility alias for location_name
orgStringOrganization name
ref_numberStringFormatted appointment reference
created_at, updated_atStringRecord creation and update timestamps
appointment_numberIntegerHuman-readable appointment number
stateStringCurrent state of the appointment (e.g., "booked", "arrived", "completed")
durationIntegerDuration in minutes
scheduled_atStringISO8601 timestamp of when the appointment is scheduled
shipping_numberStringReference number for shipping
trailer_numberStringIdentification number of the trailer
bol_numberStringBill of Lading number
carrier_nameStringName of the carrier company
carrier_idIntegerAssigned carrier company ID
driver_nameStringName of the driver
driver_phoneStringContact phone number for the driver
driver_emailStringContact email for the driver
created_byStringName of the user who created the appointment
outboundBooleanWhether this is an outbound (true) or inbound (false) appointment
drop_trailerBooleanWhether this is a drop trailer appointment
queuedBooleanWhether this appointment is in a queue
dockStringName of the assigned dock (null if yard is assigned)
yardStringName of the assigned yard (null if dock is assigned)
internal_idStringYour internal identifier for this appointment
free_untilStringStored free-until timestamp; not a calculated dock-availability time
approved_atStringISO8601 timestamp of when the appointment was approved
arrived_atStringISO8601 timestamp of when the appointment arrived
started_atStringISO8601 timestamp of when the appointment started loading/unloading
completed_atStringISO8601 timestamp of when the appointment completed loading/unloading
left_atStringISO8601 timestamp of when the appointment left
cancelled_atStringISO8601 timestamp of when the appointment was cancelled (null if active)
custom_valuesObjectKey-value pairs of custom fields
packing_listsArrayList of packing list objects associated with the appointment
notesArrayList of note objects associated with the appointment (see note format below)
documentsArrayDocuments marked safe, each with id, filename, and presigned_url
warningsArrayWarning messages when present; omitted when there are none

carrier_number and checklist_values are accepted inputs but are not returned. Response keys dock and yard differ from request keys dock_name and yard_name.

Packing-list responses contain id, po_number, customer_id, customer_name, created_at, updated_at, product, supplier, unit, booked_quantity, booked_weight, actual_quantity, actual_weight, and custom_values. Associated names and numeric values can be null. Responses do not include customer_number, product_name, or unit_name.

Code Examples​

cURL​

See cURL example
# Basic listing
curl -H "Authorization: Token YOUR_API_TOKEN" \
https://YOUR_LOCATION.datadocks.com/api/v1/appointments

# Filtered by date range and purchase order
curl -H "Authorization: Token YOUR_API_TOKEN" \
"https://YOUR_LOCATION.datadocks.com/api/v1/appointments?from=2023-10-01%2008:00%20AM&to=2023-10-05%2006:00%20PM&po_number=PO-12345"

JavaScript​

See JavaScript example
// Using fetch API with filters
const getAppointments = async (fromDate, toDate, poNumber) => {
const params = new URLSearchParams();
if (fromDate) params.append("from", fromDate);
if (toDate) params.append("to", toDate);
if (poNumber) params.append("po_number", poNumber);

const response = await fetch(
`https://YOUR_LOCATION.datadocks.com/api/v1/appointments?${params.toString()}`,
{
method: "GET",
headers: {
Authorization: "Token YOUR_API_TOKEN",
"Content-Type": "application/json",
},
}
);

// Handle potential errors
if (!response.ok) {
const errorData = await response.text();
throw new Error(
`Failed to fetch appointments: ${JSON.stringify(errorData)}`
);
}

return response.json();
};

// Processing paginated results
const getAllAppointments = async (fromDate, toDate, poNumber) => {
let currentPage = 1;
let allAppointments = [];
let hasMorePages = true;

while (hasMorePages) {
const params = new URLSearchParams({
page: currentPage.toString(),
});
if (fromDate) params.append("from", fromDate);
if (toDate) params.append("to", toDate);
if (poNumber) params.append("po_number", poNumber);

const response = await fetch(
`https://YOUR_LOCATION.datadocks.com/api/v1/appointments?${params.toString()}`,
{
method: "GET",
headers: {
Authorization: "Token YOUR_API_TOKEN",
"Content-Type": "application/json",
},
}
);

if (!response.ok) {
throw new Error("Failed to fetch appointments");
}

const appointments = await response.json();
allAppointments = [...allAppointments, ...appointments];

// Check if there are more pages
const totalPages = parseInt(response.headers.get("Total-Pages"), 10);
if (!Number.isInteger(totalPages) || totalPages < 1) {
throw new Error("Missing or invalid Total-Pages header");
}
hasMorePages = currentPage < totalPages;
currentPage++;
}

return allAppointments;
};

Sample Response​

See sample response example
[
{
"id": 1,
"location_name": "Main Warehouse",
"location": "Main Warehouse",
"org": "Example Organization",
"ref_number": "1",
"created_at": "2020-09-22T13:35:00-04:00",
"updated_at": "2020-09-25T13:35:00-04:00",
"appointment_number": 1,
"state": "left",
"duration": 120,
"shipping_number": "57886",
"trailer_number": "2222222",
"bol_number": "922",
"carrier_name": "FastCo",
"carrier_id": 12,
"driver_name": "Jason Smith",
"driver_phone": "+1 (555) 123-4567",
"driver_email": "jason.smith@fastco.example.com",
"created_by": "Sysadmin",
"outbound": false,
"drop_trailer": false,
"queued": false,
"dock": "Dock 2",
"yard": null,
"internal_id": "ERP-A12345",
"free_until": null,
"scheduled_at": "2020-09-30T06:00:00-04:00",
"approved_at": "2020-09-22T13:35:00-04:00",
"arrived_at": null,
"started_at": "2020-09-24T13:35:00-04:00",
"completed_at": "2020-09-24T13:35:00-04:00",
"left_at": "2020-09-25T13:35:00-04:00",
"cancelled_at": null,
"custom_values": {
"expected_at": "2020-10-01",
"travel_type": "Truck",
"forklift_operator": "Sue",
"inspection_passed": "1"
},
"packing_lists": [
{
"id": 4,
"created_at": "2020-09-22T13:35:00-04:00",
"updated_at": "2020-09-25T13:35:00-04:00",
"supplier": null,
"po_number": "A-2000",
"customer_name": "FishCo",
"customer_id": 34,
"product": "Trout",
"unit": "Skid",
"booked_quantity": 10,
"booked_weight": 152,
"actual_quantity": 12,
"actual_weight": 160,
"custom_values": {
"barcode": "11223344",
"dimensions": "S",
"temperature_celcius": "-5"
}
}
],
"notes": [
{
"id": 3,
"body": "First note.",
"note_type": "user_note"
}
],
"documents": []
}
]

Pagination​

The response contains up to 20 appointments per page. Headers include Current-Page, Page-Items, Total-Pages, Total-Count, and Link. Requests beyond the last page return the last page, so use the headers rather than waiting for an empty page. For detailed information about pagination headers and navigation, please refer to the Pagination documentation.

Common Gotchas and Troubleshooting​

  • Date/time parsing issues: from and to parameters are interpreted in the location's timezone. If you experience unexpected results, ensure you're providing timezone information.

    βœ… Good: from=2023-10-01T08:00:00-04:00
    βœ… Good: from=2023-10-01 08:00 AM EDT
    βœ… Good: from=2023-10-01 (midnight in the location timezone)
  • PO number filtering is case-insensitive but requires an exact match. Partial matches won't work.

    βœ… Will match: po_number=PO-12345
    βœ… Will match: po_number=po-12345 (case-insensitive)
    ❌ Won't match: po_number=12345 (partial)
  • Malformed date handling: Malformed dates do not have a consistent validation response: parsing can produce an empty result or raise an error. Prefer ISO8601 values and URL-encode query parameters.

  • Non-recurring appointments only: This endpoint only returns non-recurring appointments. Recurring appointment templates are not included in the results.

  • Performance considerations: When dealing with large date ranges, consider breaking your queries into smaller time chunks for better performance (e.g., weekly instead of monthly).

Creating an Appointment​

Purpose​

Create a new appointment at a specific dock or yard, with optional packing lists and metadata. This is the primary way to schedule new appointments in your warehouse.

Business Use Cases​

  • System Integration: Schedule appointments directly from your ERP, WMS, or TMS
  • Automated Scheduling: Create appointments based on business rules or triggers
  • Carrier Self-Service: Allow carriers to book appointments through your portal
  • Mobile Applications: Enable appointment creation from warehouse mobile apps
  • Order Fulfillment: Automatically create outbound appointments when orders are ready

HTTP Request​

POST https://[location_subdomain].datadocks.com/api/v1/appointments

Request Body​

ParameterTypeRequiredDescriptionConstraintsDefaultExample
scheduled_atStringNoISO8601 date/time when appointment is scheduledPast dates depend on permissions; not before 2000-01-01None"2023-10-15T09:00:00-04:00"
durationIntegerNoDuration in minutesMust be > 0Calculated from booking rules120
dock_nameStringNo*Name of dock to assignCase-insensitive lookupNone"Dock 1"
yard_nameStringNo*Name of yard to assignCase-insensitive lookupNone"Yard A"
carrier_nameStringNo**Name of carrier2–64 characters when requiredNone"FastCo Logistics"
carrier_idIntegerNoAccessible carrier ID; a changed nonblank ID takes precedence over name/numberMust be accessibleNone123
carrier_numberStringNoID of carrier in your systemNoneNone"CARRIER-123"
shipping_numberStringNo**Shipping reference numberNoneNone"SHIP-9876"
trailer_numberStringNo**ID of trailerNoneNone"TRAILER-456"
bol_numberStringNo**Bill of Lading number2–256 characters when nonblankNone"BOL-789"
driver_nameStringNo**Name of driver2–64 characters when nonblankNone"John Smith"
driver_phoneStringNo**Driver's contact phone10–20 characters when nonblankNone"+1 (555) 123-4567"
driver_emailStringNo**Driver's contact email7–128 characters when nonblank; no email syntax checkNone"driver@carrier.com"
outboundBooleanNoWhether appointment is outboundNoneUser preference, otherwise location preferencetrue
drop_trailerBooleanNoWhether this is a drop trailerNonefalsefalse
queuedBooleanNoWhether this is a queued appointmentNonefalsefalse
free_untilStringNoStored free-until timestampNo general comparison with scheduled_at; may be requiredNone"2023-10-15T11:00:00-04:00"
internal_idStringNoYour internal identifier for this appointmentNoneNone"ERP-A12345"
approved_atStringNoISO8601 date/time when appointment was approvedNoneNone"2023-10-14T09:00:00-04:00"
arrived_atStringNoISO8601 date/time when appointment arrivedNoneNone"2023-10-15T08:45:00-04:00"
started_atStringNoISO8601 date/time when appointment started loadingNoneNone"2023-10-15T09:05:00-04:00"
completed_atStringNoISO8601 date/time when appointment finished loadingNoneNone"2023-10-15T10:30:00-04:00"
left_atStringNoISO8601 date/time when appointment leftNoneNone"2023-10-15T10:45:00-04:00"
cancelled_atStringNoISO8601 date/time when appointment was cancelledNoneNone"2023-10-14T10:00:00-04:00"
packing_listsArrayNo**Array of packing list objectsNoneNoneSee packing list format below
notesArrayNoArray of note objectsNoneNoneSee note format below
custom_valuesObjectNo**Key-value pairs of custom fields. The key will be in lower case, with underscores in place of spaces.Based on location settingsNone{"reference": "ABC123"}
checklist_valuesObjectNoStored checklist values; not returned in responsesBased on location settingsNone{"safety_check": "Passed"}

* Assignment depends on configuration and auto-assignment. Unknown names resolve to no association rather than a dedicated not-found error; validation and auto-assignment may then apply.

** Might be required based on the Location preferences

Automatic Appointment Approval​

By default, appointments created via API may be automatically approved based on your location's settings (if scheduled_at is present). This behavior is controlled by the internal preferences (contact support to change the API auto-approval setting).

An appointment can be created without scheduled_at. Omitting duration invokes the booking defaults; relevant changes can cause recalculation. Explicit approval and later state transitions can populate approved_at even with automatic approval disabled.

Carrier Assignment Logic​

A changed, nonblank carrier_id takes precedence and must refer to an accessible company. Otherwise, carrier_number is matched case-insensitively when the name is blank or unchanged. If no number matches, it becomes the name used for subsequent assignment. A changed name generally takes precedence over number lookup. Name assignment can match or create a carrier, depending on location settings and permissions. Sending the same ID again does not count as changing it.

Packing List Format​

{
"po_number": "PO-12345",
"customer_name": "ACME Corp",
"customer_number": "CUST-001",
"product_name": "Widgets",
"unit_name": "Skid",
"booked_quantity": 10,
"booked_weight": 500,
"actual_quantity": null,
"actual_weight": null,
"custom_values": {
"temperature": "-5",
"fragile": "Yes"
}
}

Packing List Parameter Details​

ParameterTypeRequiredDescriptionConstraintsDefaultExample
idIntegerNo*ID of existing packing list (required for updates/deletion)Must be a valid ID for an existing packing list linked to the appointmentNone123
po_numberStringNo**Purchase order numberMax length varies; check location settingsNone"PO-12345"
customer_nameStringNo**Name of the customer companyLength: 2–255 chars (if provided)None"ACME Corp"
customer_numberStringNoCustomer company number (used for lookup if name/ID not given)NoneNone"CUST-001"
customer_idIntegerNoDirect ID of the customer company (overrides name/number lookup)Must be a valid Company ID accessible to the locationNone456
product_nameStringNo**Name of the productMust exist or be creatable via location settingsNone"Widgets"
unit_nameStringNo**Name of the unitMust exist or be creatable via location settingsNone"Skid"
booked_quantityDecimalNo**Quantity bookedMust be >= 0; greater than zero when required; within system limitsNone10 or 10.5
booked_weightDecimalNo**Weight bookedMust be >= 0; greater than zero when required; within system limitsNone500 or 500.75
actual_quantityDecimalNoActual quantity received/shipped (typically set on update)Must be >= 0 and within system limitsnull9 or 9.5
actual_weightDecimalNoActual weight received/shipped (typically set on update)Must be >= 0 and within system limitsnull450 or 450.25
custom_valuesObjectNo**Key-value pairs for packing list custom fields. The key will be in lower case, with underscores in place of spaces.Required custom fields are validated; unknown keys are not filtered{}{"lot_number": "A123"}
_destroyBooleanNo*Set to true to remove the packing list (on update only)Only applicable during PUT requestsfalsetrue

* Only relevant/used during PUT /appointments/:id requests when modifying or deleting existing packing lists.

** May be required based on Location field configurations. Check your Location settings for specific requirements.

Customer Assignment Logic​

When creating or updating packing lists, a changed, nonblank customer_id takes precedence and must refer to an accessible company. Sending the same ID again does not count as changing it. Otherwise:

  1. If customer_number is provided and the name is blank or unchanged, the system will:

    • Search for a company with a matching company number
    • If found, automatically populate customer_name and customer_id
    • If not found, set customer_name to match the provided customer_number
  2. A changed name generally takes precedence over number lookup. Subsequent name assignment can match or create a customer, depending on settings and permissions.

Product and Unit Handling​

When specifying products and units, case-insensitive matching ignores surrounding whitespace. Automatic creation is disabled by default and can be enabled separately through support. Unknown names do not always cause an error: required-field rules and product validation determine whether the request fails, and unresolved associations can remain unset. Product-specific unit restrictions also apply.

On update, omitting product_name or unit_name preserves that association. An empty value requests clearing it, subject to validation.

Note Format​

{
"body": "Driver requires lift gate"
}

New notes are automatically associated with the current API user. Include id to edit an existing note, or id and _destroy: true to request deletion. Use notes rather than the legacy scalar note key, which has no model writer.

Responses also return a read-only note_type for each note:

ValueMeaning
user_noteA note written by a person. This is the default, and the only type you can create.
cancellation_noteRecorded by DataDocks when an appointment is cancelled with a reason from the Booking Portal.

note_type is assigned by DataDocks and is ignored if you send it. Cancellation notes cannot be deleted β€” they document what happened to the appointment, so a _destroy sent for one is rejected and the whole update fails.

Response​

A successful request returns a 200 OK response with the full appointment object, including the generated id and other system-assigned values.

Code Examples​

cURL​

See cURL example
curl -H "Authorization: Token YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"appointment": {
"scheduled_at": "2023-10-15T09:00:00-04:00",
"duration": 120,
"dock_name": "Dock 1",
"carrier_name": "FastCo Logistics",
"carrier_number": "CARRIER-123",
"trailer_number": "TRAILER-456",
"driver_name": "John Smith",
"driver_phone": "+1 (555) 123-4567",
"driver_email": "john.smith@fastco.example.com",
"outbound": false,
"internal_id": "ERP-A12345",
"packing_lists": [
{
"po_number": "PO-12345",
"product_name": "Widgets",
"unit_name": "Skid",
"booked_quantity": 10,
"booked_weight": 500
}
],
"notes": [
{
"body": "Driver requires lift gate"
}
],
"custom_values": {
"reference": "ABC123",
"priority": "High"
},
"checklist_values": {
"safety_check": "Scheduled",
"dock_seal": "Required"
}
}
}' \
https://YOUR_LOCATION.datadocks.com/api/v1/appointments

JavaScript​

See JavaScript example
const createAppointment = async (
apiToken,
locationSubdomain,
appointmentData
) => {
try {
const response = await fetch(
`https://${locationSubdomain}.datadocks.com/api/v1/appointments`,
{
method: "POST",
headers: {
Authorization: `Token ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ appointment: appointmentData }),
}
);

if (!response.ok) {
const errorData = await response.text();
const errorMessages = errorData || `HTTP ${response.status}`;

throw new Error(`Failed to create appointment:\n${errorMessages}`);
}

return await response.json();
} catch (error) {
console.error("API Error:", error);
throw error;
}
};

// Example usage with complete data
const appointmentData = {
scheduled_at: "2023-10-15T09:00:00-04:00",
duration: 120,
dock_name: "Dock 1",
carrier_name: "FastCo Logistics",
carrier_number: "CARRIER-123",
trailer_number: "TRAILER-456",
driver_name: "John Smith",
driver_phone: "+1 (555) 123-4567",
driver_email: "john.smith@fastco.example.com",
outbound: false,
internal_id: "ERP-A12345",
packing_lists: [
{
po_number: "PO-12345",
product_name: "Widgets",
unit_name: "Skid",
booked_quantity: 10,
booked_weight: 500,
},
],
notes: [
{
body: "Driver requires lift gate",
},
],
custom_values: {
reference: "ABC123",
priority: "High",
},
checklist_values: {
safety_check: "Scheduled",
dock_seal: "Required",
},
};

// Create the appointment
createAppointment("YOUR_API_TOKEN", "your-warehouse", appointmentData)
.then((appointment) => {
console.log("Created appointment:", appointment.id);
// Process the newly created appointment
})
.catch((error) => {
// Handle any errors
});

Error Handling​

Error CodeDescriptionPossible Cause
400Bad RequestMalformed request, such as a missing appointment wrapper
404Not FoundNested record ID not found in the accessible scope
422Unprocessable EntityBusiness validation failed (e.g., scheduling conflict, full dock)
401UnauthorizedInvalid or missing API token
403ForbiddenInsufficient permissions to create appointments

Model validation failures generally return 422. Unknown dock, yard, product, or unit names do not inherently produce 404. Unpaid or deleted locations can return 402; throttled requests return 429. Error bodies can be JSON, plain text, or empty. Nested packing-list error keys can include an index.

Sample Error Response​

{
"errors": {
"driver_name": ["is too short (minimum is 2 characters)"]
}
}

Common Gotchas and Troubleshooting​

  • Dock vs. Yard Assignment: Prefer providing either dock_name or yard_name. If both associations are set, a changed yard wins; otherwise the dock wins. On update, an omitted name preserves the assignment unless other model behavior changes it, while an empty string requests clearing it.

  • Time Formats: Explicit timezone information is recommended. Without it, times are interpreted in the location's timezone.

    βœ… Good: "2023-10-15T09:00:00-04:00"
    βœ… Accepted: "2023-10-15T09:00:00" (location timezone)
  • Product/Unit Creation: Automatic creation is disabled by default. Unknown names may fail validation or leave an unset association, depending on configuration. Ask support to enable automatic creation if needed.

  • Customer Lookups: Customer numbers are matched case-insensitively. For example, "CUST-001" and "cust-001" will both match the same customer.

  • Packing List Deletion: To delete a packing list when updating an appointment, include the packing list's id and set _destroy: true.

    {
    "id": 123,
    "_destroy": true
    }
  • Recurring Appointments: The API does not support creating or modifying recurring appointments. Use the web interface for these operations.

  • Custom Fields: Required custom fields are validated, but unknown keys are not automatically filtered out.

  • Automatic Approval: Check with your administrator about your location's automatic approval settings. Disabling API auto-approval skips the internal auto-approval path; explicit approval and other transitions can still set approval timestamps.

Performance Optimization Tips​

  • Batch Your Requests: When creating multiple appointments, space your API calls to avoid rate limiting
  • Minimize Custom Values: Only include custom fields that are actually needed
  • Preload Resources: Make sure your products, units, and carriers exist before referencing them
  • Validate Locally: Validate data formats client-side to minimize failed requests

Retrieving a Single Appointment​

Purpose​

Get detailed information about a specific appointment by ID. Use this endpoint when you need comprehensive data about a particular appointment, including its current status, associated packing lists, and metadata.

Business Use Cases​

  • Status Tracking: Check the current state of an appointment
  • Arrival Monitoring: View arrival and departure times
  • Document Verification: Check attached documents and packing lists
  • Carrier Information: Retrieve driver and trailer details
  • Custom Field Access: Access location-specific custom fields; checklist values are not returned

HTTP Request​

GET https://[location_subdomain].datadocks.com/api/v1/appointments/:id

Path Parameters​

ParameterTypeRequiredDescription
idIntegerYesUnique ID of the appointment

Response​

A successful request returns a 200 OK response with the full appointment object in the same format as the list response.

Code Examples​

cURL​

See cURL example
curl -H "Authorization: Token YOUR_API_TOKEN" \
https://YOUR_LOCATION.datadocks.com/api/v1/appointments/123

JavaScript​

See JavaScript example
const getAppointment = async (appointmentId) => {
try {
const response = await fetch(
`https://YOUR_LOCATION.datadocks.com/api/v1/appointments/${appointmentId}`,
{
method: "GET",
headers: {
Authorization: "Token YOUR_API_TOKEN",
"Content-Type": "application/json",
},
}
);

if (!response.ok) {
if (response.status === 404) {
throw new Error(`Appointment #${appointmentId} not found`);
}

const errorData = await response.text();
throw new Error(
`Failed to retrieve appointment: ${JSON.stringify(errorData)}`
);
}

return await response.json();
} catch (error) {
console.error("Error fetching appointment:", error);
throw error;
}
};

// Usage
getAppointment(123)
.then((appointment) => {
console.log(
`Appointment #${appointment.id} is currently ${appointment.state}`
);

// Check if appointment is completed
if (appointment.completed_at) {
const completionTime = new Date(appointment.completed_at);
console.log(`Completed at: ${completionTime.toLocaleString()}`);
}

// Calculate total appointment duration
if (appointment.left_at && appointment.arrived_at) {
const arrivedTime = new Date(appointment.arrived_at);
const leftTime = new Date(appointment.left_at);
const durationMinutes = Math.round((leftTime - arrivedTime) / 60000);
console.log(`Total facility dwell time: ${durationMinutes} minutes`);
}
})
.catch((error) => {
// Handle error gracefully
if (error.message.includes("not found")) {
// Handle not found case
} else {
// Handle other errors
}
});

Error Responses​

Error CodeDescriptionPossible Cause
404Not FoundAppointment ID doesn't exist
401UnauthorizedInvalid or missing API token
403ForbiddenInsufficient permissions to view

Limitations​

  • Only non-recurring appointments can be retrieved with this endpoint
  • If you need to view recurring appointment templates, use the web interface

Updating an Appointment​

Purpose​

Update an existing appointment's details, status, or associated data. This endpoint accepts the fields listed above, subject to configuration and permissions. Documents and signatures cannot be uploaded through this endpoint.

Business Use Cases​

  • Status Updates: Mark appointments as arrived, started, completed, or left
  • Schedule Changes: Adjust appointment times or durations
  • Detail Updates: Update carrier information, trailer numbers, or shipping references
  • Add/Update Packing Lists: Modify the products, quantities, or custom values
  • Note Management: Add notes to document important information

HTTP Request​

PUT https://[location_subdomain].datadocks.com/api/v1/appointments/:id

Path Parameters​

ParameterTypeRequiredDescription
idIntegerYesUnique ID of the appointment

Request Body​

The request body accepts the same parameters as the create endpoint. Only the fields you want to change need to be included.

Status Transition Flow​

When updating appointment status fields, they should generally follow this sequence:

scheduled_at β†’ approved_at β†’ arrived_at β†’ started_at β†’ completed_at β†’ left_at

Permissions and validation govern changes. State is derived from timestamps; state itself is not a writable field. When state changes, callbacks can fill missing earlier timestamps or clear later ones. For example, setting left_at fills missing approval, arrival, start, and completion timestamps with the departure time. Send measured timestamps when available.

Common Update Scenarios​

Marking an Appointment as Arrived​

{
"appointment": {
"arrived_at": "2023-10-15T08:45:00-04:00",
"trailer_number": "TRAILER-789"
}
}

Updating Packing List Quantities​

{
"appointment": {
"packing_lists": [
{
"id": 123,
"actual_quantity": 9,
"actual_weight": 450
}
]
}
}

Adding a New Note​

{
"appointment": {
"notes": [
{
"body": "Truck arrived with damaged seal. Inspection completed."
}
]
}
}

Removing a Packing List​

{
"appointment": {
"packing_lists": [
{
"id": 123,
"_destroy": true
}
]
}
}

Marking an Appointment as Completed​

{
"appointment": {
"completed_at": "2023-10-15T10:30:00-04:00",
"left_at": "2023-10-15T10:45:00-04:00",
"checklist_values": {
"dock_condition": "Clean",
"paperwork_complete": "Yes"
}
}
}

Code Examples​

cURL​

See cURL example
curl -H "Authorization: Token YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-X PUT \
-d '{
"appointment": {
"trailer_number": "UPDATED-TRAILER-789",
"arrived_at": "2023-10-15T08:45:00-04:00",
"notes": [
{
"body": "Truck arrived with different trailer number than scheduled."
}
]
}
}' \
https://YOUR_LOCATION.datadocks.com/api/v1/appointments/123

JavaScript​

See JavaScript example
const updateAppointment = async (appointmentId, updateData) => {
try {
const response = await fetch(
`https://YOUR_LOCATION.datadocks.com/api/v1/appointments/${appointmentId}`,
{
method: "PUT",
headers: {
Authorization: "Token YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ appointment: updateData }),
}
);

if (!response.ok) {
const errorData = await response.text();
throw new Error(
`Failed to update appointment: ${JSON.stringify(errorData)}`
);
}

return await response.json();
} catch (error) {
console.error("Error updating appointment:", error);
throw error;
}
};

// Example: Mark appointment as arrived and started
const markAppointmentArrived = async (appointmentId, arrivalTime) => {
const now = arrivalTime || new Date().toISOString();

try {
const updatedAppointment = await updateAppointment(appointmentId, {
arrived_at: now,
notes: [
{
body: `Truck arrived at ${new Date(now).toLocaleTimeString()}`,
},
],
});

console.log(
`Successfully marked appointment #${updatedAppointment.id} as arrived`
);
return updatedAppointment;
} catch (error) {
console.error(
`Failed to mark appointment #${appointmentId} as arrived:`,
error
);
throw error;
}
};

// Example: Update packing list quantities
const updatePackingListQuantities = async (
appointmentId,
packingListId,
actualQuantity,
actualWeight
) => {
try {
const updatedAppointment = await updateAppointment(appointmentId, {
packing_lists: [
{
id: packingListId,
actual_quantity: actualQuantity,
actual_weight: actualWeight,
},
],
});

console.log(
`Successfully updated packing list #${packingListId} quantities`
);
return updatedAppointment;
} catch (error) {
console.error(`Failed to update packing list quantities:`, error);
throw error;
}
};

Error Handling​

Error CodeDescriptionPossible Cause
400Bad RequestMalformed request; model validation generally returns 422
404Not FoundAppointment ID doesn't exist
422Unprocessable EntityBusiness validation failed (e.g., invalid transition)
401UnauthorizedInvalid or missing API token
403ForbiddenInsufficient permissions to update

Important Limitations​

  • Recurring Appointments: The API does not support updating recurring appointment templates. The endpoint will return an error if you attempt to update a recurring appointment.

  • Partial Updates: Only include the fields you want to update. Omitted ordinary fields are preserved, but callbacks can change timestamps, duration, and associations. JSON objects such as custom_values and checklist_values replace the whole attribute rather than merging individual keys; preserve existing keys explicitly.

  • Nested Objects: When updating nested objects like packing lists, you must include the id of the existing object to update it, otherwise a new object will be created. Omitting existing entries does not delete them.

  • Deletion Not Supported: There is no API endpoint to delete appointments. To cancel an appointment, set the cancelled_at field instead.

Status Transition Best Practices​

Send actual timestamps in their logical sequence when available. A later-precedence timestamp controls the state even if you update an earlier one. The normal sequence is:

  1. Create appointment (scheduled_at)
  2. Approve appointment (approved_at)
  3. Mark as arrived (arrived_at)
  4. Mark as started (started_at)
  5. Mark as completed (completed_at)
  6. Mark as left (left_at)

Each status update should include relevant metadata, such as actual quantities for packing lists when marking as completed.

Appointment Lifecycle & Workflow​

Understanding the appointment lifecycle is critical for effective API integration. Each appointment follows a defined state machine that governs its progression through your warehouse operations.

Appointment States Visual Workflow​

The following diagram illustrates the typical lifecycle of an appointment:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Booked │───▢│ Arrived │───▢│ Started │───▢│ Completed │───▢│ Left β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
└─────────────────────────────▢│ Cancelled β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

State Transition Events​

Each transition is triggered by updating the corresponding timestamp field:

FromToTimestamp FieldDescription
Needs bookingPendingscheduled_atAppointment with scheduled at
PendingBookedapproved_atAppointment approval (may be automatic)
BookedArrivedarrived_atTruck has arrived at facility
ArrivedStartedstarted_atLoading/unloading has begun
StartedCompletedcompleted_atLoading/unloading is finished
CompletedLeftleft_atTruck has departed from facility
AnyCancelledcancelled_atAppointment cancelled

The returned states are needs_booking, pending, booked, arrived, started, completed, left, and cancelled. Timestamp precedence is cancelled_at, left_at, completed_at, started_at, arrived_at, approved_at, then scheduled_at. Scheduling may automatically produce booked rather than pending. Clearing cancelled_at lets the state be recalculated from remaining timestamps, subject to permissions.

Data Collection During Transitions​

Each state transition is an opportunity to collect important operational data:

Arrived​

  • Update driver information if changed
  • Verify trailer number
  • Record arrival time for on-time performance
  • Document any discrepancies or issues

Started​

  • Document dock assignment changes
  • Record loading/unloading start time
  • Begin tracking operational duration

Completed​

  • Record actual quantities received/shipped
  • Document quality issues or discrepancies
  • Collect signatures or confirmations through the appropriate application workflow
  • Upload documents through the application; these endpoints do not accept uploads

Left​

  • Calculate total facility time
  • Record departure time for yard management
  • Complete checklist items

Implementation Best Practices​

  1. Validate State Transitions: Ensure your application validates the logical sequence of transitions
  2. Include Metadata: Each status update should include relevant metadata
  3. Add Context Notes: Automatically generate notes to document each transition
  4. Trigger Notifications: Use webhooks to notify relevant stakeholders of important transitions
  5. Record Timing Metrics: Track how long appointments spend in each state

Security Best Practices​

Protecting your warehouse data requires implementing proper security measures for API access.

Secure Storage​

  • Never hardcode tokens: Store tokens in environment variables or secure credential stores
  • Encrypt at rest: Ensure tokens are encrypted when stored
  • Use secrets management: Consider tools like HashiCorp Vault or AWS Secrets Manager

Connection Security​

  • Always use HTTPS: Never make API calls over unencrypted connections
  • Validate certificates: Verify SSL/TLS certificates on all API calls

Additional Security Measures​

  • Set up monitoring: Establish alerts for suspicious API activity
  • Create a security incident response plan: Know what to do if a token is compromised

Appointment Deletion (Not Supported)​

Important Limitation​

The Appointments API does not support deletion of appointments. Use cancellation to retain the appointment record.

Alternatives to Deletion​

Instead of deleting appointments, use one of these approaches:

  • Cancel the appointment by setting the cancelled_at field
  • Update appointment details if you need to change scheduling information
  • Maintain metadata in custom fields to track appointment status in your system

Cancelling an Appointment​

Cancel by setting cancelled_at. Any note you send alongside it is an ordinary user_note: cancellation notes are only produced by the Booking Portal's cancellation flow, where the facility can require carriers to give a reason.

See cURL example
curl -H "Authorization: Token YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-X PUT \
-d '{
"appointment": {
"cancelled_at": "2023-10-14T15:30:00-04:00",
"notes": [
{
"body": "Cancelled due to weather conditions."
}
]
}
}' \
https://YOUR_LOCATION.datadocks.com/api/v1/appointments/123

API Cookbook: Common Scenarios​

This section provides ready-to-use recipes for common integration scenarios. Copy, adapt, and use these examples to accelerate your integration.

Recipe: Sync Outbound Shipments from ERP​

This recipe demonstrates how to automatically create appointments for outbound shipments when they're ready in your ERP system.

See JavaScript example
/**
* Create an outbound appointment from shipping data
* @param {Object} shipment - Shipping data from ERP
* @returns {Object} - Created appointment
*/
async function createOutboundAppointment(shipment) {
// Calculate appointment duration based on item count
const duration = Math.max(30, shipment.items.length * 15); // Minimum 30 minutes

// Format scheduled time with timezone
const scheduledDate = new Date(shipment.plannedShipDate);
const scheduledTime = scheduledDate.toISOString();

// Build packing lists from shipment items
const packingLists = shipment.items.map((item) => ({
po_number: item.purchaseOrderNumber,
product_name: item.productName,
unit_name: item.unitType,
booked_quantity: item.quantity,
booked_weight: item.weight,
customer_number: shipment.customerId,
customer_name: shipment.customerName,
}));

// Create appointment object
const appointmentData = {
scheduled_at: scheduledTime,
duration: duration,
outbound: true,
dock_name: shipment.preferredDock || "Shipping Dock",
shipping_number: shipment.shipmentId,
carrier_name: shipment.carrierName,
carrier_number: shipment.carrierId,
internal_id: `SHIP-${shipment.id}`,
custom_values: {
origin: "ERP-SYNC",
priority: shipment.priority,
department: shipment.department,
},
packing_lists: packingLists,
notes: [
{
body: `Automated outbound appointment for shipment ${shipment.shipmentId}.`,
},
],
};

// Call DataDocks API
const response = await fetch(
`https://YOUR_LOCATION.datadocks.com/api/v1/appointments`,
{
method: "POST",
headers: {
Authorization: "Token YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ appointment: appointmentData }),
}
);

if (!response.ok) {
const errorData = await response.text();
throw new Error(
`Failed to create appointment: ${JSON.stringify(errorData)}`
);
}

return await response.json();
}

Recipe: Real-time Dock Utilization Dashboard​

This recipe shows scheduled dock utilization and facility dwell time. Supply explicit date boundaries in the location's timezone and operating minutes for that interval. Arrival-to-departure measures facility dwell, not dock occupancy; automatically filled timestamps may not represent measured activity.

See JavaScript example
/**
* Calculate dock utilization metrics
* @param {string} fromDate - Start with location timezone offset
* @param {string} toDate - Exclusive end with location timezone offset
* @param {number} operatingMinutes - Operating minutes per dock for the interval
* @returns {Object} - Scheduled utilization and facility dwell by dock
*/
async function calculateDockUtilization(fromDate, toDate, operatingMinutes) {
if (!(operatingMinutes > 0)) throw new Error("Provide operating minutes for this interval");

// Fetch appointments for the supplied interval
const appointments = await getAllAppointments(fromDate, toDate);

// Group by dock
const dockMap = {};

appointments.forEach((appt) => {
if (!appt.dock || appt.state === "cancelled") return;

if (!dockMap[appt.dock]) {
dockMap[appt.dock] = {
totalAppointments: 0,
completedAppointments: 0,
scheduledMinutes: 0,
facilityMinutes: 0,
appointmentsList: [],
};
}

// Calculate scheduled minutes
const scheduledMinutes = appt.duration || 0;

// Calculate facility dwell where arrival and departure are present
let facilityMinutes = 0;
if (appt.arrived_at && appt.left_at) {
const arrivedTime = new Date(appt.arrived_at);
const leftTime = new Date(appt.left_at);
facilityMinutes = Math.round((leftTime - arrivedTime) / 60000);
}

// Update dock metrics
dockMap[appt.dock].totalAppointments++;
dockMap[appt.dock].scheduledMinutes += scheduledMinutes;
dockMap[appt.dock].facilityMinutes += facilityMinutes;

if (appt.completed_at) {
dockMap[appt.dock].completedAppointments++;
}

dockMap[appt.dock].appointmentsList.push({
id: appt.id,
state: appt.state,
scheduled_at: appt.scheduled_at,
duration: appt.duration,
carrier_name: appt.carrier_name,
});
});

// Calculate utilization rates
const dockUtilization = {};
const businessHours = operatingMinutes;

Object.keys(dockMap).forEach((dockName) => {
const dock = dockMap[dockName];

dockUtilization[dockName] = {
...dock,
scheduledUtilization:
((dock.scheduledMinutes / businessHours) * 100).toFixed(
1
) + "%",
completionRate: dock.totalAppointments
? ((dock.completedAppointments / dock.totalAppointments) * 100).toFixed(
1
) + "%"
: "0%",
};
});

return dockUtilization;
}

Recipe: Automatic Truck Arrival Processing​

This recipe demonstrates how to update appointment status when a truck arrives, including updating driver information.

See JavaScript example
/**
* Process truck arrival
* @param {string} appointmentId - Appointment ID
* @param {Object} arrivalData - Data collected during arrival
* @returns {Object} - Updated appointment
*/
async function processTruckArrival(appointmentId, arrivalData) {
const arrivalTime = new Date().toISOString();

// Update appointment with arrival info
const appointmentUpdate = {
arrived_at: arrivalTime,
trailer_number: arrivalData.trailerNumber || null,
driver_name: arrivalData.driverName || null,
driver_phone: arrivalData.driverPhone || null,
driver_email: arrivalData.driverEmail || null,
notes: [
{
body: `Truck arrived at ${new Date(
arrivalTime
).toLocaleString()}. Check-in processed by gate security.`,
},
],
custom_values: {
...(arrivalData.customValues || {}),
actual_arrival_time: arrivalTime,
},
};

// Add delay note if applicable
if (arrivalData.isDelayed) {
appointmentUpdate.notes.push({
body: `Truck arrived ${arrivalData.delayMinutes} minutes after scheduled time. Reason: ${arrivalData.delayReason}`,
});
}

// Call DataDocks API
const response = await fetch(
`https://YOUR_LOCATION.datadocks.com/api/v1/appointments/${appointmentId}`,
{
method: "PUT",
headers: {
Authorization: "Token YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ appointment: appointmentUpdate }),
}
);

if (!response.ok) {
const errorData = await response.text();
throw new Error(
`Failed to update appointment: ${JSON.stringify(errorData)}`
);
}

return await response.json();
}

Recipe: Bulk Appointment Status Updates​

This recipe sends a separate request for each update. Share a rate budget across workers using the same credentials; these requests are not an atomic batch.

See JavaScript example
/**
* Process a batch of appointment status updates
* @param {Array} updates - Array of updates to process
* @returns {Object} - Results of updates
*/
async function processBatchStatusUpdates(updates) {
const results = {
successful: [],
failed: [],
};

// Pace this worker below both API-specific limits; other workers must share the budget.
for (const update of updates) {
await new Promise((resolve) => setTimeout(resolve, 1000));
try {
const { appointmentId, status, timestamp, notes } = update;

// Determine which status field to update
let statusUpdate = {};
switch (status) {
case "arrived":
statusUpdate = { arrived_at: timestamp || new Date().toISOString() };
break;
case "started":
statusUpdate = { started_at: timestamp || new Date().toISOString() };
break;
case "completed":
statusUpdate = {
completed_at: timestamp || new Date().toISOString(),
};
break;
case "left":
statusUpdate = { left_at: timestamp || new Date().toISOString() };
break;
default:
throw new Error(`Invalid status: ${status}`);
}

// Add notes if provided
if (notes) {
statusUpdate.notes = [{ body: notes }];
}

// Call API to update appointment
const response = await fetch(
`https://YOUR_LOCATION.datadocks.com/api/v1/appointments/${appointmentId}`,
{
method: "PUT",
headers: {
Authorization: "Token YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ appointment: statusUpdate }),
}
);

if (!response.ok) {
const errorData = await response.text();
throw new Error(JSON.stringify(errorData));
}

const updatedAppointment = await response.json();
results.successful.push({
appointmentId,
status,
result: updatedAppointment,
});

} catch (error) {
results.failed.push({
appointmentId: update.appointmentId,
status: update.status,
error: error.message,
});
}
}

return results;
}

Performance Optimization​

Optimize your API integration with these performance tips:

Efficient Data Loading​

  • Limit results with date filtering: Always provide from and to parameters for list endpoints
  • Cache results: Store and reuse data that doesn't change frequently
  • Use pagination: Request only the data you need by page

Request Optimization​

  • Batch updates: Group related updates together rather than making multiple API calls
  • Implement concurrency control: Limit parallel requests to avoid rate limiting
  • Use documented responses: Conditional GET and ETag support are not part of the current API contract

Network Performance​

  • Keep-alive connections: Reuse connections when making multiple requests
  • Send JSON request bodies: Gzip-compressed request bodies are not documented as supported
  • Implement exponential backoff: When encountering rate limits, use exponential backoff strategy

Troubleshooting Common Issues​

Authentication Problems​

Symptoms​

  • 401 Unauthorized errors
  • "Access denied" error messages

Solutions​

  1. Verify your API token is correct and has not been replaced
  2. Ensure token has proper permissions
  3. Check that the API user has access to the location you're accessing
  4. Verify proper header format: Authorization: Token YOUR_API_TOKEN

Rate Limiting​

Symptoms​

  • 429 Too Many Requests errors
  • Sudden failure of requests that previously worked

Solutions​

  1. Share a budget across clients using the same credentials: API-specific limits are 120 requests per minute and 5,000 per hour, grouped by the authorization-header value. General request throttles also apply
  2. Add delays between requests in batch operations
  3. Honor the ISO8601 X-RateLimit-Reset header and use backoff; 429 responses also include X-RateLimit-Limit and X-RateLimit-Remaining
  4. Before retrying a create after an ambiguous failure, reconcile using internal_id or another reference to avoid duplicates; internal_id is not an idempotency key

Date/Time Issues​

Symptoms​

  • Appointments created at wrong times
  • Filtering not returning expected results

Solutions​

  1. Always specify timezone in date strings (e.g., 2023-10-15T09:00:00-04:00)
  2. Be aware of daylight saving time changes
  3. Use ISO8601 format for all date/time values
  4. For date range queries, ensure from is before to

Data Validation Errors​

Symptoms​

  • 422 Unprocessable Entity errors
  • Specific field errors in response

Solutions​

  1. Check error response for specific field validation errors
  2. Verify location-required fields are provided. scheduled_at can be omitted, and duration is calculated when omitted
  3. Ensure referenced resources (docks, products, units) exist
  4. Validate data formats client-side before sending

Help and Support​

Finding Error Solutions​

If you're experiencing issues not covered in this documentation:

  1. Check the error response: Most API errors include specific information about what went wrong
  2. Review API limitations: Some operations (like recurring appointments) are not supported
  3. Test in smaller steps: Break down complex operations to identify the specific issue
  4. Check rate limits: Check the 120 requests per minute and 5,000 per hour API-specific limits, plus general request throttles

Getting Help​

For additional assistance with the DataDocks API:

When contacting support, please include:

  • Your location subdomain
  • Request details (method, endpoint, parameters)
  • Full error response
  • Timestamp of the error