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. 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​

ParameterTypeRequiredDescriptionExample
pageIntegerNoPage number for pagination (defaults to 1)page=2
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

Response Format​

The response contains a paginated array of appointment objects, each with the following fields:

FieldTypeDescription
idIntegerUnique identifier for the appointment
appointment_numberIntegerHuman-readable appointment number
stateStringCurrent state of the appointment (e.g., "scheduled", "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_numberStringIdentifier for the carrier in your system
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
dock_nameStringName of the assigned dock (null if yard is assigned)
yard_nameStringName of the assigned yard (null if dock is assigned)
internal_idStringYour internal identifier for this appointment
free_untilStringISO8601 timestamp of when the dock/yard becomes free
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
checklist_valuesObjectKey-value pairs of checklist items
packing_listsArrayList of packing list objects associated with the appointment
notesArrayList of note objects associated with the appointment
documentsArrayList 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 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
    ❌ 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​

ParameterTypeRequiredDescriptionConstraintsDefaultExample
scheduled_atStringNoISO8601 date/time when appointment is scheduledMust be in the futureNone"2023-10-15T09:00:00-04:00"
durationIntegerNoDuration in minutesMust be > 0None120
dock_nameStringNo*Name of dock to assignMust exist at locationNone"Dock 1"
yard_nameStringNo*Name of yard to assignMust exist at locationNone"Yard A"
carrier_nameStringNo**Name of carrierNoneNone"FastCo Logistics"
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 numberNoneNone"BOL-789"
driver_nameStringNo**Name of driverNoneNone"John Smith"
driver_phoneStringNo**Driver's contact phoneNoneNone"+1 (555) 123-4567"
driver_emailStringNoDriver's contact emailValid email formatNone"driver@carrier.com"
outboundBooleanNoWhether appointment is outboundNonePer location settingstrue
drop_trailerBooleanNoWhether this is a drop trailerNonefalsefalse
queuedBooleanNoWhether this is a queued appointmentNonefalsefalse
free_untilStringNoISO8601 date/time when dock/yard becomes freeMust be after scheduled_atNone"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_listsArrayNoArray of packing list objectsNoneNoneSee packing list format below
notesArrayNoArray of note objectsNoneNoneSee note format below
custom_valuesObjectNo**Key-value pairs of custom fieldsBased on location settingsNone{"reference": "ABC123"}
checklist_valuesObjectNoKey-value pairs of checklist itemsBased on location settingsNone{"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​

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-64 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 and within system limitsNone10 or 10.5
booked_weightDecimalNo**Weight bookedMust be >= 0 and 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 fieldsKeys must match configured custom fields for packing lists{}{"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 packing lists, you can provide either customer_name, customer_number, or both:

  1. If only customer_number is provided, 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. 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 CodeDescriptionPossible Cause
400Bad RequestMissing required fields or invalid values
404Not FoundReferenced dock/yard/product doesn't exist at this location
422Unprocessable EntityBusiness validation failed (e.g., scheduling conflict, full dock)
401UnauthorizedInvalid or missing API token
403ForbiddenInsufficient 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 or yard_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​

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.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 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 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​

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

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 CodeDescriptionPossible Cause
400Bad RequestInvalid values or format
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. 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:

  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:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Scheduled │───▢│ 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
ScheduledApprovedapproved_atAppointment approval (may be automatic)
ApprovedArrivedarrived_atTruck has arrived at facility
ArrivedStartedstarted_atLoading/unloading has begun
StartedCompletedcompleted_atLoading/unloading is finished
CompletedLeftleft_atTruck has departed from facility
AnyCancelledcancelled_atAppointment 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​

  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. 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 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 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​

  1. Verify your API token is correct and not expired
  2. Ensure token has proper permissions
  3. Check if token is specific 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. Implement rate limiting in your client (60 requests per minute per IP)
  2. Add delays between requests in batch operations
  3. Implement exponential backoff for retries
  4. Distribute requests across multiple API tokens if possible

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 required fields are provided (scheduled_at, duration)
  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: You may be exceeding the 60 requests per minute limit

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