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:
- Request and Get your API token from support
- Find your location subdomain (e.g.,
your-warehouse.datadocks.com
) - 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"
}
}'
- VoilΓ ! You've created your first appointment. Check your DataDocks dashboard to see it.
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 are organization-specific and will be displayed in the Audit logs as "Datadocks API - YOUR_ORG_NAME"
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 must be properly formatted with timezone information
- Only non-recurring appointments are returned in listing endpoints
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β
Parameter | Type | Required | Description | Example |
---|---|---|---|---|
page | Integer | No | Page number for pagination (defaults to 1) | page=2 |
po_number | String | No | Filter by purchase order number (case-insensitive exact match) | po_number=PO-12345 |
from | String | No | Filter appointments scheduled on or after this date/time (location timezone) | from=2023-10-01 08:00 AM |
to | String | No | Filter appointments scheduled before this date/time (location timezone) | to=2023-10-05 06:00 PM |
Response Formatβ
The response contains a paginated array of appointment objects, each with the following fields:
Field | Type | Description |
---|---|---|
id | Integer | Unique identifier for the appointment |
appointment_number | Integer | Human-readable appointment number |
state | String | Current state of the appointment (e.g., "scheduled", "arrived", "completed") |
duration | Integer | Duration in minutes |
scheduled_at | String | ISO8601 timestamp of when the appointment is scheduled |
shipping_number | String | Reference number for shipping |
trailer_number | String | Identification number of the trailer |
bol_number | String | Bill of Lading number |
carrier_name | String | Name of the carrier company |
carrier_number | String | Identifier for the carrier in your system |
driver_name | String | Name of the driver |
driver_phone | String | Contact phone number for the driver |
driver_email | String | Contact email for the driver |
created_by | String | Name of the user who created the appointment |
outbound | Boolean | Whether this is an outbound (true) or inbound (false) appointment |
drop_trailer | Boolean | Whether this is a drop trailer appointment |
queued | Boolean | Whether this appointment is in a queue |
dock_name | String | Name of the assigned dock (null if yard is assigned) |
yard_name | String | Name of the assigned yard (null if dock is assigned) |
internal_id | String | Your internal identifier for this appointment |
free_until | String | ISO8601 timestamp of when the dock/yard becomes free |
approved_at | String | ISO8601 timestamp of when the appointment was approved |
arrived_at | String | ISO8601 timestamp of when the appointment arrived |
started_at | String | ISO8601 timestamp of when the appointment started loading/unloading |
completed_at | String | ISO8601 timestamp of when the appointment completed loading/unloading |
left_at | String | ISO8601 timestamp of when the appointment left |
cancelled_at | String | ISO8601 timestamp of when the appointment was cancelled (null if active) |
custom_values | Object | Key-value pairs of custom fields |
checklist_values | Object | Key-value pairs of checklist items |
packing_lists | Array | List of packing list objects associated with the appointment |
notes | Array | List of note objects associated with the appointment |
documents | Array | List of document objects associated with the appointment |
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.json();
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("X-Total-Pages"), 10);
hasMorePages = currentPage < totalPages;
currentPage++;
}
return allAppointments;
};
Sample Responseβ
See sample response example
[
{
"id": 1,
"appointment_number": 1,
"state": "left",
"duration": 120,
"shipping_number": "57886",
"trailer_number": "2222222",
"bol_number": "922",
"carrier_name": "FastCo",
"carrier_number": "FC123",
"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_name": "Dock 2",
"yard_name": 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"
},
"checklist_values": {
"safety_check": "Passed",
"dock_seal_status": "Good",
"temperature": "-5"
},
"packing_lists": [
{
"id": 4,
"po_number": "A-2000",
"customer_name": "FishCo",
"customer_number": "FC001",
"product_name": "Trout",
"unit_name": "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."
}
],
"documents": []
}
]
Paginationβ
The response is paginated to manage data volume. For detailed information about pagination headers and navigation, please refer to the Pagination documentation.
Common Gotchas and Troubleshootingβ
-
Date/time parsing issues:
from
andto
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
β Bad: from=2023-10-01 -
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: If you provide improperly formatted dates, the API currently does not return a validation error. Ensure your date strings are properly formatted to avoid unexpected filter results.
-
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.
Decision Tree: When to Create an Appointment via APIβ
βββββββββββββββββββ
β Need to scheduleβ
β appointment? β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ Yes βββββββββββββββββββ
β Is this part ββββββββββββββΆβ Use regular β
β of a recurring β β booking UI β
β pattern? β β β
ββββββββββ¬βββββββββ βββββββββββββββββββ
β No
βΌ
βββββββββββββββββββ Yes βββββββββββββββββββ
β Creating many ββββββββββββββΆβ Consider using β
β appointments at β β bulk import via β
β once? β β the web UI β
ββββββββββ¬βββββββββ βββββββββββββββββββ
β No
βΌ
βββββββββββββββββββ
β Perfect for β
β the API! β
βββββββββββββββββββ
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β
Parameter | Type | Required | Description | Constraints | Default | Example |
---|---|---|---|---|---|---|
scheduled_at | String | No | ISO8601 date/time when appointment is scheduled | Must be in the future | None | "2023-10-15T09:00:00-04:00" |
duration | Integer | No | Duration in minutes | Must be > 0 | None | 120 |
dock_name | String | No* | Name of dock to assign | Must exist at location | None | "Dock 1" |
yard_name | String | No* | Name of yard to assign | Must exist at location | None | "Yard A" |
carrier_name | String | No** | Name of carrier | None | None | "FastCo Logistics" |
carrier_number | String | No | ID of carrier in your system | None | None | "CARRIER-123" |
shipping_number | String | No** | Shipping reference number | None | None | "SHIP-9876" |
trailer_number | String | No** | ID of trailer | None | None | "TRAILER-456" |
bol_number | String | No** | Bill of Lading number | None | None | "BOL-789" |
driver_name | String | No** | Name of driver | None | None | "John Smith" |
driver_phone | String | No** | Driver's contact phone | None | None | "+1 (555) 123-4567" |
driver_email | String | No | Driver's contact email | Valid email format | None | "driver@carrier.com" |
outbound | Boolean | No | Whether appointment is outbound | None | Per location settings | true |
drop_trailer | Boolean | No | Whether this is a drop trailer | None | false | false |
queued | Boolean | No | Whether this is a queued appointment | None | false | false |
free_until | String | No | ISO8601 date/time when dock/yard becomes free | Must be after scheduled_at | None | "2023-10-15T11:00:00-04:00" |
internal_id | String | No | Your internal identifier for this appointment | None | None | "ERP-A12345" |
approved_at | String | No | ISO8601 date/time when appointment was approved | None | None | "2023-10-14T09:00:00-04:00" |
arrived_at | String | No | ISO8601 date/time when appointment arrived | None | None | "2023-10-15T08:45:00-04:00" |
started_at | String | No | ISO8601 date/time when appointment started loading | None | None | "2023-10-15T09:05:00-04:00" |
completed_at | String | No | ISO8601 date/time when appointment finished loading | None | None | "2023-10-15T10:30:00-04:00" |
left_at | String | No | ISO8601 date/time when appointment left | None | None | "2023-10-15T10:45:00-04:00" |
cancelled_at | String | No | ISO8601 date/time when appointment was cancelled | None | None | "2023-10-14T10:00:00-04:00" |
packing_lists | Array | No | Array of packing list objects | None | None | See packing list format below |
notes | Array | No | Array of note objects | None | None | See note format below |
custom_values | Object | No** | Key-value pairs of custom fields | Based on location settings | None | {"reference": "ABC123"} |
checklist_values | Object | No | Key-value pairs of checklist items | Based on location settings | None | {"safety_check": "Passed"} |
* Either dock_name
or yard_name
is typically required, depending on your location's configuration.
** 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 (to disable this behaviour pplease contact support).
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β
Parameter | Type | Required | Description | Constraints | Default | Example |
---|---|---|---|---|---|---|
id | Integer | No* | ID of existing packing list (required for updates/deletion) | Must be a valid ID for an existing packing list linked to the appointment | None | 123 |
po_number | String | No** | Purchase order number | Max length varies; check location settings | None | "PO-12345" |
customer_name | String | No** | Name of the customer company | Length: 2-64 chars (if provided) | None | "ACME Corp" |
customer_number | String | No | Customer company number (used for lookup if name/ID not given) | None | None | "CUST-001" |
customer_id | Integer | No | Direct ID of the customer company (overrides name/number lookup) | Must be a valid Company ID accessible to the location | None | 456 |
product_name | String | No** | Name of the product | Must exist or be creatable via location settings | None | "Widgets" |
unit_name | String | No** | Name of the unit | Must exist or be creatable via location settings | None | "Skid" |
booked_quantity | Decimal | No** | Quantity booked | Must be >= 0 and within system limits | None | 10 or 10.5 |
booked_weight | Decimal | No** | Weight booked | Must be >= 0 and within system limits | None | 500 or 500.75 |
actual_quantity | Decimal | No | Actual quantity received/shipped (typically set on update) | Must be >= 0 and within system limits | null | 9 or 9.5 |
actual_weight | Decimal | No | Actual weight received/shipped (typically set on update) | Must be >= 0 and within system limits | null | 450 or 450.25 |
custom_values | Object | No** | Key-value pairs for packing list custom fields | Keys must match configured custom fields for packing lists | {} | {"lot_number": "A123"} |
_destroy | Boolean | No* | Set to true to remove the packing list (on update only) | Only applicable during PUT requests | false | true |
* 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 packing lists, you can provide either customer_name
, customer_number
, or both:
-
If only
customer_number
is provided, the system will:- Search for a company with a matching company number
- If found, automatically populate
customer_name
andcustomer_id
- If not found, set
customer_name
to match the providedcustomer_number
-
If both are provided, the system will use the provided values
Product and Unit Handlingβ
When specifying products and units, case-insensitive matching is used to find existing records. If a product or unit does not exist the API will return an error for unknown products or units by default. If you want to create product or unit instead in this case please contact support to disable this validation and set up creation of the product or unit instead
Note Formatβ
{
"body": "Driver requires lift gate"
}
Notes are automatically associated with the current API user.
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.json();
// Convert errors to a more readable format
const errorMessages = Object.entries(errorData.errors || {})
.map(([field, messages]) => `${field}: ${messages.join(", ")}`)
.join("\n");
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 Code | Description | Possible Cause |
---|---|---|
400 | Bad Request | Missing required fields or invalid values |
404 | Not Found | Referenced dock/yard/product doesn't exist at this location |
422 | Unprocessable Entity | Business validation failed (e.g., scheduling conflict, full dock) |
401 | Unauthorized | Invalid or missing API token |
403 | Forbidden | Insufficient permissions to create appointments |
Sample Error Responseβ
{
"errors": {
"scheduled_at": ["can't be blank", "must be in the future"],
"dock_name": ["Dock 'Dock 99' not found at this location"],
"packing_lists.product_name": [
"Product 'Unknown Product' not found and automatic creation is disabled"
]
}
}
Common Gotchas and Troubleshootingβ
-
Dock vs. Yard Assignment: Provide either
dock_name
oryard_name
, but not both. If both are provided, you may get unexpected results. -
Time Formats: All times must include timezone information. Without it, times will be interpreted in the location's timezone, which may not be what you intend.
β Good: "2023-10-15T09:00:00-04:00"
β Bad: "2023-10-15T09:00:00" -
Product/Unit Creation: By default, the API will return an error if you reference a product or unit that doesn't exist. If you need automatic creation, ask your DataDocks administrator to enable the appropriate settings.
-
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: Only custom fields configured for your location can be used. Others will be silently ignored.
-
Automatic Approval: Check with your administrator about your location's automatic approval settings. If disabled, appointments created via API will require manual approval.
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 and checklists
HTTP Requestβ
GET https://[location_subdomain].datadocks.com/api/v1/appointments/:id
Path Parametersβ
Parameter | Type | Required | Description |
---|---|---|---|
id | Integer | Yes | Unique 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.json();
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 time at dock: ${durationMinutes} minutes`);
}
})
.catch((error) => {
// Handle error gracefully
if (error.message.includes("not found")) {
// Handle not found case
} else {
// Handle other errors
}
});
Error Responsesβ
Error Code | Description | Possible Cause |
---|---|---|
404 | Not Found | Appointment ID doesn't exist |
401 | Unauthorized | Invalid or missing API token |
403 | Forbidden | Insufficient 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 allows you to modify any aspect of an appointment throughout its lifecycle.
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β
Parameter | Type | Required | Description |
---|---|---|---|
id | Integer | Yes | Unique 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
While you can update any field at any time, following this logical progression helps maintain data integrity.
Common Update Scenariosβ
Marking an Appointment as Arrivedβ
{
"appointment": {
"arrived_at": "2023-10-15T08:45:00-04:00",
"trailer_number": "TRAILER-789" // Updated trailer information
}
}
Updating Packing List Quantitiesβ
{
"appointment": {
"packing_lists": [
{
"id": 123, // ID of existing packing list
"actual_quantity": 9, // Actual received quantity
"actual_weight": 450 // Actual received weight
}
]
}
}
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 // Set to true to remove this packing list
}
]
}
}
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.json();
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 Code | Description | Possible Cause |
---|---|---|
400 | Bad Request | Invalid values or format |
404 | Not Found | Appointment ID doesn't exist |
422 | Unprocessable Entity | Business validation failed (e.g., invalid transition) |
401 | Unauthorized | Invalid or missing API token |
403 | Forbidden | Insufficient 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. Any field not included will remain unchanged.
-
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. -
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β
While the API allows updating status fields in any order, we recommend following this sequence for the most accurate tracking:
- Create appointment (
scheduled_at
) - Approve appointment (
approved_at
) - Mark as arrived (
arrived_at
) - Mark as started (
started_at
) - Mark as completed (
completed_at
) - 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:
ββββββββββββββ ββββββββββββββ ββββββββββββββ ββββββββββββββ ββββββββββββββ
β Scheduled βββββΆβ Arrived βββββΆβ Started βββββΆβ Completed βββββΆβ Left β
βββββ βββββββββ ββββββββββββββ ββββββββββββββ ββββββββββββββ ββββββββββββββ
β
β ββββββββββββββ
βββββββββββββββββββββββββββββββΆβ Cancelled β
ββββββββββββββ
State Transition Eventsβ
Each transition is triggered by updating the corresponding timestamp field:
From | To | Timestamp Field | Description |
---|---|---|---|
Needs booking | Pending | scheduled_at | Appointment with scheduled at |
Scheduled | Approved | approved_at | Appointment approval (may be automatic) |
Approved | Arrived | arrived_at | Truck has arrived at facility |
Arrived | Started | started_at | Loading/unloading has begun |
Started | Completed | completed_at | Loading/unloading is finished |
Completed | Left | left_at | Truck has departed from facility |
Any | Cancelled | cancelled_at | Appointment cancelled (terminal state) |
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
- Upload relevant documents
Leftβ
- Calculate total facility time
- Record departure time for yard management
- Complete checklist items
Implementation Best Practicesβ
- Validate State Transitions: Ensure your application validates the logical sequence of transitions
- Include Metadata: Each status update should include relevant metadata
- Add Context Notes: Automatically generate notes to document each transition
- Trigger Notifications: Use webhooks to notify relevant stakeholders of important transitions
- 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
Related Endpointsβ
- Products API - Manage products referenced in packing lists
- Purchase Orders API - Manage POs associated with appointments
- Companies API - Manage carriers and customers
- Webhooks - Get real-time notifications about appointment changes
Appointment Deletion (Not Supported)β
Important Limitationβ
The Appointments API does not support deletion of appointments. This design decision was made to maintain audit history and data integrity.
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β
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.json();
throw new Error(
`Failed to create appointment: ${JSON.stringify(errorData)}`
);
}
return await response.json();
}
Recipe: Real-time Dock Utilization Dashboardβ
This recipe shows how to fetch and analyze appointment data for real-time dock utilization metrics.
See JavaScript example
/**
* Calculate dock utilization metrics
* @returns {Object} - Utilization metrics by dock
*/
async function calculateDockUtilization() {
// Get today's date
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
// Format dates for API
const fromDate = today.toISOString().split("T")[0] + " 00:00:00";
const toDate = tomorrow.toISOString().split("T")[0] + " 00:00:00";
// Fetch today's appointments
const appointments = await getAllAppointments(fromDate, toDate);
// Group by dock
const dockMap = {};
appointments.forEach((appt) => {
if (!appt.dock_name) return;
if (!dockMap[appt.dock_name]) {
dockMap[appt.dock_name] = {
totalAppointments: 0,
completedAppointments: 0,
scheduledMinutes: 0,
actualMinutes: 0,
appointmentsList: [],
};
}
// Calculate scheduled minutes
const scheduledMinutes = appt.duration || 0;
// Calculate actual minutes if completed
let actualMinutes = 0;
if (appt.arrived_at && appt.left_at) {
const arrivedTime = new Date(appt.arrived_at);
const leftTime = new Date(appt.left_at);
actualMinutes = Math.round((leftTime - arrivedTime) / 60000);
}
// Update dock metrics
dockMap[appt.dock_name].totalAppointments++;
dockMap[appt.dock_name].scheduledMinutes += scheduledMinutes;
dockMap[appt.dock_name].actualMinutes += actualMinutes;
if (appt.completed_at) {
dockMap[appt.dock_name].completedAppointments++;
}
dockMap[appt.dock_name].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 = 8 * 60; // Assuming 8 business hours in minutes
Object.keys(dockMap).forEach((dockName) => {
const dock = dockMap[dockName];
dockUtilization[dockName] = {
...dock,
scheduledUtilization:
Math.min(100, (dock.scheduledMinutes / businessHours) * 100).toFixed(
1
) + "%",
actualUtilization:
Math.min(100, (dock.actualMinutes / businessHours) * 100).toFixed(1) +
"%",
completionRate: dock.totalAppointments
? ((dock.completedAppointments / dock.totalAppointments) * 100).toFixed(
1
) + "%"
: "0%",
efficiency:
dock.scheduledMinutes && dock.actualMinutes
? ((dock.scheduledMinutes / dock.actualMinutes) * 100).toFixed(1) +
"%"
: "N/A",
};
});
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.json();
throw new Error(
`Failed to update appointment: ${JSON.stringify(errorData)}`
);
}
return await response.json();
}
Recipe: Bulk Appointment Status Updatesβ
This recipe shows how to efficiently update multiple appointments' statuses in a single batch process.
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: [],
};
// Process updates sequentially to avoid rate limits
for (const update of updates) {
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.json();
throw new Error(JSON.stringify(errorData));
}
const updatedAppointment = await response.json();
results.successful.push({
appointmentId,
status,
result: updatedAppointment,
});
// Add a small delay between requests to avoid rate limiting
await new Promise((resolve) => setTimeout(resolve, 100));
} 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
andto
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 conditional requests: For future API versions, we plan to support ETag and conditional GET requests
Network Performanceβ
- Keep-alive connections: Reuse connections when making multiple requests
- Compress request bodies: For large payloads, consider using gzip compression
- Implement exponential backoff: When encountering rate limits, use exponential backoff strategy
Troubleshooting Common Issuesβ
Authentication Problemsβ
Symptomsβ
- 401 Unauthorized errors
- "Invalid token" error messages
Solutionsβ
- Verify your API token is correct and not expired
- Ensure token has proper permissions
- Check if token is specific to the location you're accessing
- 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β
- Implement rate limiting in your client (60 requests per minute per IP)
- Add delays between requests in batch operations
- Implement exponential backoff for retries
- Distribute requests across multiple API tokens if possible
Date/Time Issuesβ
Symptomsβ
- Appointments created at wrong times
- Filtering not returning expected results
Solutionsβ
- Always specify timezone in date strings (e.g.,
2023-10-15T09:00:00-04:00
) - Be aware of daylight saving time changes
- Use ISO8601 format for all date/time values
- For date range queries, ensure
from
is beforeto
Data Validation Errorsβ
Symptomsβ
- 422 Unprocessable Entity errors
- Specific field errors in response
Solutionsβ
- Check error response for specific field validation errors
- Verify required fields are provided (scheduled_at, duration)
- Ensure referenced resources (docks, products, units) exist
- Validate data formats client-side before sending
Help and Supportβ
Finding Error Solutionsβ
If you're experiencing issues not covered in this documentation:
- Check the error response: Most API errors include specific information about what went wrong
- Review API limitations: Some operations (like recurring appointments) are not supported
- Test in smaller steps: Break down complex operations to identify the specific issue
- Check rate limits: You may be exceeding the 60 requests per minute limit
Getting Helpβ
For additional assistance with the DataDocks API:
- Documentation: Check the full API documentation
- Support: Contact support at support@datadocks.com
When contacting support, please include:
- Your location subdomain
- Request details (method, endpoint, parameters)
- Full error response
- Timestamp of the error