Appointment Scheduling is a major point of friction for carriers, brokers, and shippers alike. Complex and nuanced appointment scheduling processes lead to inefficiencies when procuring and managing appointments across the freight industry. The SSC aims to simplify the integration of systems across the fragmented ecosystem that exists today. We anticipate a standard set of communication protocols will enable shippers and carriers to more seamlessly connect with each others' systems, enabling efficient data sharing and greater efficiencies across the supply chain.
The primary objectives of the SSC are to (1) define an API standard for sharing scheduling information, (2) eliminate manual processes by automating interactions where possible, (3) implement the standardized interfaces and integrations across core systems, and (4) advocate for the standard across the industry. The SSC aims to partner with shippers, carriers, brokers, and solutions providers to drive towards a standard. The purpose of this document is to start the conversation and to provide an overview of the key workflows that the scheduling API will need to support. The SSC will continue to refine documentation informed by feedback from the industry.
Aligning as industry leaders on a set of standards will simplify the integration of systems across the fragmented ecosystem between shippers, carriers, and intermediaries. All such parties will benefit from simpler interfaces and integrations.
Given that existing TMS and Appointment Scheduling solutions, are the source of truth for appointment data and appointment restrictions, the SSC's proposal is to simplify carrier-side appointment logic and nuance, and empower the TMS's and Appointment Scheduling Vendor's to implement necessary rules and validations for appointment booking.
Core Systems Involved: The first phase of this initiative will primarily focus on defining standards to facilitate appointment scheduling transactions between carriers/brokers and shipper TMS dock scheduling applications. We aim to extend these standards to standalone dock scheduling solutions (i.e. non-TMS scheduling solutions) in the next phase of this initiative.
Load Types: The first phase of this initiative will focus on over-the-road full truckload. Multi-stop loads (i.e. loads that pickup or dropoff at multiple locations) are also included as in scope.
Equipment Types: This first phase of this initiative will support the 3 primary full truckload equipment types: Van, Reefer, Flatbed.
Loading Types: Live, Drop, and Preload appointment scenarios will be supported.
With intersystem communication via API, usually, the expectation is for synchronous responses whereby the client contacts the server and a response is given within a reasonable service-level agreement (SLA), usually sub-second. However, there will be occasions when the response cannot be provided back in realtime. This could be due to manual tasks that must be executed by users of the system which the server supports or because the process to retrieve the correct answer takes additional time. In these scenarios, an asynchronous response option to the client needs to be provided whereby the client receives the response at a later time. Asynchronous communication options include webhooks, callbacks, and polling. With the appointment scheduling specification, the SSC does not want to prescribe a communication method or expectation but to provide a specification that allows for all possible options so the most appropriate one can be used, given the situation. In the following section, we describe four mechanisms that may be implemented to establish communication for data exchange.
Synchronous communication is the RECOMMENDED option out of all API communication options as it provides the client with an immediate response and is easiest to implement. When using synchronous APIs, blocking behavior on the server side is expected since the client will pause waiting for a response. See the "Best Practices" section for more details on implementation recommendations.
Asynchronous communication is preferred in the appointment scheduling space for systems which require more time to process requests before responding to clients. In contrast to synchronous communication where the client waits for an immediate response from the server before continuing, asynchronous communication enables clients to submit requests and continue their work while the server processes their requests in the background, with the intent to receive responses when they become available. This section explains different implementation options for asynchronous communication.
A webhook is an HTTP-based callback function that allows lightweight, event-driven communication between two applications. To set up a webhook, the client gives a unique URL to the server API and specifies which event it wants to know about. Once the webhook is set up, the client no longer needs to poll the server; the server will automatically send the relevant payload to the client's webhook URL when the specified event occurs. This is the RECOMMENDED way to communicate asynchronously.
A callback is a mechanism provided by clients to be executed by the server once a specific process is completed. Similar to webhooks, this mechanism is commonly used in APIs that require a later response once a process is invoked. Whereas webhooks are registered once prior to processes being invoked, clients provide a callback URL to the server with each request where results are expected to be sent. This approach for communication is RECOMMENDED when a one-time registration is not possible.
Unlike webhooks and callback functions, polling is when the client, after submitting a request for a process to be executed, is responsible for contacting the server repeatedly to see if the process has been executed or not. The subsequent polling requests can be at regular intervals, random intervals, or exponential backoff. This option is NOT RECOMMENDED to be used except in cases where asynchronous responses are required but webhook or callback functions cannot be provided by the TMS. We RECOMMEND establishing reasonable expectations with clients up front regarding polling frequency and duration, and implement appropriate rate limiting mechanisms to guard against misbehavior. See the "Best Practices" section for more details on implementation recommendations.
Enforcing API security involves the following components:
Identification establishes the context for who is making a request and what the request is for. This is especially important in the cases where the clients are acting as a broker on behalf of multiple carriers. It is RECOMMENDED that the information be passed as an additional API header field that is supplemental to the API Header as defined by the SSC.
The SSC does not define any requirements on specific authentication methods that may be available to support inbound API request authentication. However, it is RECOMMENDED by the SSC to use the authorization header as defined in RFC7235 with the OAuth 2.0 Framework following RFC6749.
API implementers are expected to publicly document the authentication method(s) they support. Clients are expected to comply with the security requirements of a given TMS / appointment scheduling solution in order to use the API.
An example using the Authorization header is provided below.
Authorization:
Bearer
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
The authorization of a call should include validating the authentication of the user, ensuring the user is able to act for a specific identifier, and is able to ultimately act on the request details sent in the request. The SSC currently does not require either party to standardize on a specific authorization method, for example, Access Control Lists (ACLs), Role Based Access Control (RBAC), Attribute Based Access Control (ABAC), etc. However we strongly encourage all requests to be validated.
Capability URLs provide the ability for implementers of webhooks/callback functions to communicate to clients. When following best practices, the URL created by the client will be as secure as the client would want. If a client chooses to use Capability URLs, we RECOMMEND following the best practices provided by W3C. Below are several principles to consider when using capability URLs.
Using HTTPS does not prevent all exposure of the URL but, generally, it is best practice to use HTTPS as much as possible to prevent accidental exposure of private information in the request.
If any suspicious activity is observed while using a capability URL, it is RECOMMENDED to create a new capability URL and re-register the URL with each TMS. It is up to the client to determine what activity is considered "suspicious."
The risk of a capability URL being discovered increases with time and repeated usage. The best way to prevent accidental discovery outside of IP or referrer defenses is to expire and change the URL periodically. The frequency of these changes should be discussed with your security team and follow any best practices defined by your organization.
For the best integration experience for all parties, the SSC encourages the use of the API specification as-is. However, implementers may have specific requirements which may necessitate the addition of custom properties to the specification. To ensure custom fields do not overlap with future potential additions, it is encouraged to follow these practices for any implementer-specific additions.
Implementers SHOULD only add custom properties to the API Headers that extends the "context" of the request being placed. Practices should follow standards placed in RFC7231 Section 5. Additional custom properties that can be included in the header is limited to the following:
Security Authentication Credentials (RFC7231 Section 5.4)
Who the request is on behalf of. This can be handled in the following:
Who the request is for. This can be handled in the follow ways:
Furthermore, implementers SHOULD NOT add custom properties to the API headers that should be a part of the body of the API specification.
Similar to RFC6648 Section 3, creators of new properties to be used:
In summary, adding a new property to the request body or headers SHOULD be avoided if possible as it forces more customization between the clients. Feel free to contact the SSC for additional field suggestions to add to the specification.
In this section, we will provide a list of best practices that should be considered when building an API that provides synchronous or asynchronous responses.
While discouraged, the SSC is aware there may be times when an API implementer may need to add additional custom properties to request headers and/or request and response bodies. However, the SSC strongly discourages any such customizations that vary based on other aspects of the request context, such as which customer or facility the request is for. For example, requiring a field 'foo' when making a request for one customer but requiring a different field 'bar' when making a request to a different customer should be avoided. Similarly, defining supported values for a field that vary based on the customer or facility should also be avoided. Such variations greatly increase the complexity and effort required for a client to successfully integrate with scheduling systems at scale.
Within the requirements, you may notice that it is required for TMSs / appointment scheduling solutions to provide a way for carriers / brokers to pass information to uniquely identify a load or a stop. The API specification defines these identifiers as follows:
However, none of these unique identifiers are required. This is intentional as the SSC does not want to prescribe for a system what information they may or may not need. Ultimately, it will be up to the discretion of the system to clearly define via their own documentation if specific identifiers laid out by the SSC are required or optional for their specific implementation. As a rule of thumb, you should provide additional information about the load or stop if you are uncertain the information you originally planned to provide will adequately identify both.
It is a good practice to set a reasonable SLA for clients to expect a synchronous response back from a request. SLAs and response times SHOULD be sub-second to no more than a few seconds at maximum. In scenarios where more than a few seconds are required to respond, asynchronous communication options should be considered.
There are risks that higher-than-expected traffic will deny other callers access to the server, including a caller issuing too many requests, misconfigured systems, or system bugs. To protect against this, it is RECOMMENDED that a TMS have account-level access control to rate-limit callers.
IANA keeps an official registry of HTTP status codes that lists the corresponding RFCs that define each status. When handling status codes, servers SHOULD use codes according to the semantics described in the corresponding RFC. Using the status codes correctly is important as it tells the client how to proceed after making a request. Definitions of the 4xx, 5xx status codes are not defined in this specification and are expected to be followed according to their standard RFC definitions.
In an attempt to make interoperability as easy as possible, the SSC proposes to use capability URLs to better enable authentication when accessing a client system while also allowing the client to control the level of security they provide by following best practices as defined by W3C. See the "Capability URLs" section for more details.
When a reschedule appointment request is rejected, the expectation is that the existing scheduled appointment is maintained. If the communication about the rejection back to the carrier / broker must be returned asynchronously via a Webhook, the SSC recommends that two responses are sent back where one is the failure of the request and the second is the reaffirmation to the carrier / broker that they still have the appointment.
If the TMS supports using a stop sequence number to fetch or schedule appointments, it is possible that the stop sequence number changes, which is considered a load-level event. The appointment API defined by the SSC focuses on appointment change events, not load-level events. Depending on TMS implementation, a carrier who is registered to receive notifications about a stop's appointments may not receive a notification that the stop sequence number has changed.
It is possible that certain identifiers may not be known to the client. This specification supposes both parties have resolved any identifiers before utilizing this API. To help clients resolve IDs, implementers MAY create endpoints that can be used to resolve these IDs for their clients. APIs created like this would be outside of the SSC ownership and should be treated as helpers for their clients. If you believe the API could provide value to the SSC, please contact a member of the SSC to further discuss the use case.
One example is location identifiers. To keep the scheduling API focused on scheduling concerns, the SSC is not addressing fragmentation in resolving addresses and locations. In the case that a TMS requires a locationId to be provided for a scheduling workflow, it is expected that the TMS provides a solution for the clients which will then use the locationId in the scheduling API.
When canceling an existing appointment, scenarios could arise where the appointment that is being canceled has pending requests from the carrier / broker, e.g., carrier / broker submits a reschedule appointment request prior to making a decision to cancel the appointment altogether. In general, it makes sense that canceling an appointment would also result in the cancelation of any pending requests related to that appointment. However, TMSs / appointment scheduling solutions may have unique implementations that may require additional efforts internally or from carriers / brokers to resolve these scenarios. For these reasons, the SSC has chosen not to prescribe expected behavior, but encourages TMSs / appointment scheduling solutions to clearly outline their expectations in their documentation that they provide to carriers / brokers who choose to integrate with their technology.
All feedback and discussions will be handled within the GitHub repository. Please use the discussions tab to leave feedback on the specification. If you find any specific issues with the specifications, please create an issue in the issues tab.
Appointment-based APIs. These APIs are RPC-based. Client requests that match the spec are expected to return 2xx response codes. If there is an issue with the response, the error will be reflected in the response object. Try to distinguish errors represented from valid client interactions (such as using an invalid primary reference number) from failures through the protocol (such as using fields that do not match the specification). Read the best practices section above on HTTP Codes for more information about HTTP protocol semantics.
As a carrier / broker, I expect to fetch a list of available appointment times for a particular load on a particular day at a particular stop in a TMS / appointment scheduling solution.
Figure 1: fetchAvailableAppointments with Synchronous Response
Figure 2: fetchAvailableAppointments with Asynchronous Polling
Figure 3: fetchAvailableAppointments with Asynchronous Response via Callback URL or Webhook
callbackUrl | string <uri> (callbackUrl) This URL should be structured as a Capability URL. Details about the best practices for Capability URLs can be found here. |
required | Load and Stop Identifiers (object) or Appointment Identifier (object) or Pending Request Identifier (object) Identifiers for identifying a stop or pending request in the TMS / appointment scheduling solution. |
Date Range (object) or Exact Date (object) Optional date restrictions by which to filter appointments |
This example is the bare minimum required to retrieve available appointments
{- "identifiers": {
- "primaryReferenceNumber": "LOAD1234",
- "stopSequenceNumber": 1
}
}
Example of a success response
{- "status": "SUCCESS",
- "appointments": [
- {
- "id": "1234",
- "appointmentType": "AUTOMATIC",
- "arrivalWindow": {
- "startDateTime": "2023-04-15T12:00:00.000-05:00",
- "duration": "PT1H"
}, - "loadingType": "LIVE"
}
]
}
Example of a success response
{- "status": "SUCCESS",
- "appointments": [
- {
- "id": "1234",
- "appointmentType": "AUTOMATIC",
- "arrivalWindow": {
- "startDateTime": "2023-04-15T12:00:00.000-05:00",
- "duration": "PT1H"
}, - "loadingType": "LIVE"
}
]
}
As a carrier / broker, I expect to schedule available appointment times for a particular load on a particular day at a particular stop in a TMS / appointment scheduling solution.
Figure 4: scheduleAppointment with Synchronous Response for AUTOMATIC Appointment Types
Figure 5: scheduleAppointment with Synchronous Response for CARRIER_CONFIRMATION_REQUIRED Appointment Types
Figure 6: scheduleAppointment with Asynchronous Response for DEFERRED Appointment Types via Polling
Figure 7: scheduleAppointment with Asynchronous Response for DEFERRED Appointment Types via Callback URL or Webhook
callbackUrl | string <uri> (callbackUrl) This URL should be structured as a Capability URL. Details about the best practices for Capability URLs can be found here. |
required | object (commonIdentifiers) Identifiers for identifying a stop in the TMS / appointment scheduling solution. |
required | Appointment ID (object) or Appointment Details (object) (clientRequestedAppointment) |
reasonCode | string Reason code explaining why the appointment is being scheduled |
comment | string Additional comment associated with the schedule request. Comments are unstructured and TMS-specific. |
This example is the bare minimum required to schedule/reschedule an appointment
{- "identifiers": {
- "primaryReferenceNumber": "LOAD1234",
- "stopSequenceNumber": 1
}, - "preferredAppointment": {
- "id": "1234",
- "loadingType": "LIVE"
}
}
Example of a success response
{- "status": "SUCCESS",
- "appointment": {
- "id": "1234",
- "arrivalWindow": {
- "startDateTime": "2023-04-15T12:00:00.000-05:00",
- "duration": "PT1H"
}
}, - "appointmentStatus": "CONFIRMED",
- "appointmentConfirmationNumber": "C1234"
}
Example of a success response
{- "status": "SUCCESS",
- "appointment": {
- "id": "1234",
- "arrivalWindow": {
- "startDateTime": "2023-04-15T12:00:00.000-05:00",
- "duration": "PT1H"
}
}, - "appointmentStatus": "CONFIRMED",
- "appointmentConfirmationNumber": "C1234"
}
As a carrier / broker, I expect to reschedule available appointment times for a particular load on a particular day at a particular stop in a TMS / appointment scheduling solution.
Figure 8: rescheduleAppointment with Synchronous Response for AUTOMATIC Appointment Types
Figure 9: rescheduleAppointment with Asynchronous Response for DEFERRED Appointment Types via Polling
Figure 10: rescheduleAppointment with Asynchronous Response for DEFERRED Appointment Types via Callback URL or Webhook
callbackUrl | string <uri> (callbackUrl) This URL should be structured as a Capability URL. Details about the best practices for Capability URLs can be found here. |
required | Load and Stop Identifiers (object) or Appointment Identifier (object) (appointmentIdentifier) |
required | Appointment ID (object) or Appointment Details (object) (clientRequestedAppointment) |
reasonCode | string Reason code explaining why the appointment is being rescheduled |
comment | string Additional comment associated with the reschedule request. Comments are unstructured and TMS-specific. |
This example is the bare minimum required to schedule/reschedule an appointment
{- "identifiers": {
- "primaryReferenceNumber": "LOAD1234",
- "stopSequenceNumber": 1
}, - "preferredAppointment": {
- "id": "1234",
- "loadingType": "LIVE"
}
}
Example of a success response
{- "status": "SUCCESS",
- "appointment": {
- "id": "1234",
- "arrivalWindow": {
- "startDateTime": "2023-04-15T12:00:00.000-05:00",
- "duration": "PT1H"
}
}, - "appointmentStatus": "CONFIRMED",
- "appointmentConfirmationNumber": "C1234"
}
Example of a success response
{- "status": "SUCCESS",
- "appointment": {
- "id": "1234",
- "arrivalWindow": {
- "startDateTime": "2023-04-15T12:00:00.000-05:00",
- "duration": "PT1H"
}
}, - "appointmentStatus": "CONFIRMED",
- "appointmentConfirmationNumber": "C1234"
}
As a carrier / broker, I expect to be able to cancel an existing appointment in a TMS / appointment scheduling solution.
Figure 11: cancelAppointment with Synchronous Response
required | Load and Stop Identifiers (object) or Appointment Identifier (object) (appointmentIdentifier) |
reasonCode | string The reason for canceling the appointment. |
comment | string Additional comment associated with the cancel request. Comments are unstructured and TMS-specific. |
This example is the bare minimum required to cancel an appointment
{- "identifiers": {
- "appointmentId": "1234"
}
}
Example of a success response
{- "status": "SUCCESS"
}
As a carrier / broker, I expect to fetch the existing appointment details for a particular load at a particular stop in a TMS / appointment scheduling solution.
Figure 12: fetchAppointmentDetails with Synchronous Response
required | Load and Stop Identifiers (object) or Appointment Identifier (object) (appointmentIdentifier) |
This example is the bare minimum required to fetch appointment details
{- "identifiers": {
- "appointmentId": "1234"
}
}
Example of a success response
{- "status": "SUCCESS",
- "appointment": {
- "id": "1234",
- "arrivalWindow": {
- "startDateTime": "2023-04-15T12:00:00.000-05:00",
- "duration": "PT1H"
}, - "loadingType": "LIVE"
}, - "appointmentConfirmationNumber": "C1234",
- "appointmentStatus": "CONFIRMED"
}
APIs related to registering a webhook. These APIs are REST-based, following the collection pattern.
More information and details about webhooks can be found in the asynchronous communication options and best practices from the documentation above.
As a carrier / broker, I expect to fetch a list of current webhook event subscriptions for appointment management in a TMS / appointment scheduling solution.
Figure 13: Get All Subscriptions via Webhook
This example is the bare minimum required
{- "subscriptions": [
- {
- "subscriptionId": "1234",
- "eventTypes": [
- "appointment-changed",
- "fetch-available-appointments"
],
}
]
}
As a carrier / broker, I expect to subscribe to webhook events for appointment management in a TMS / appointment scheduling solution.
Figure 15: Create Subscriptions via Webhook
eventTypes required | Array of strings (webhookEventType) Items Enum: "appointment-changed" "fetch-available-appointments" |
callbackUrl | string <uri> (callbackUrl) This URL should be structured as a Capability URL. Details about the best practices for Capability URLs can be found here. |
This example is the bare minimum required
{- "eventTypes": [
- "appointment-changed",
- "fetch-available-appointments"
],
}
This example is the bare minimum required
{- "subscriptionId": "1234",
- "eventTypes": [
- "appointment-changed",
- "fetch-available-appointments"
],
}
As a carrier / broker, I expect to fetch a specific current webhook event subscription for appointment management in a TMS / appointment scheduling solution.
Figure 14: Get Specific Subscription via Webhook
subscriptionId required | string The ID of the subscription |
This example is the bare minimum required
{- "subscriptionId": "1234",
- "eventTypes": [
- "appointment-changed",
- "fetch-available-appointments"
],
}
As a carrier / broker, I expect to update subscribed webhook events for appointment management in a TMS / appointment scheduling solution.
Figure 16: Update Subscriptions via Webhook
subscriptionId required | string The ID of the subscription |
subscriptionId | string Unique ID for the subscription in the TMS / appointment scheduling solution. |
eventTypes | Array of strings (webhookEventType) Items Enum: "appointment-changed" "fetch-available-appointments" The type of events that will be sent to the registered callback. |
callbackUrl | string <uri> (callbackUrl) This URL should be structured as a Capability URL. Details about the best practices for Capability URLs can be found here. |
This example is the bare minimum required
{- "subscriptionId": "1234",
- "eventTypes": [
- "appointment-changed",
- "fetch-available-appointments"
],
}
This example is the bare minimum required
{- "subscriptionId": "1234",
- "eventTypes": [
- "appointment-changed",
- "fetch-available-appointments"
],
}
As a carrier / broker, I expect to delete subscribed webhook events for appointment management in a TMS / appointment scheduling solution.
Figure 17: Delete Subscriptions via Webhook
subscriptionId required | string The ID of the subscription |
Detailed descriptions of the webhook events that TMS / appointment scheduling solutions can send.
As a TMS / appointment scheduling solution, I expect to send asynchronous information about changes to existing appointments to carriers / brokers who are subscribed to the "appointment-changed" webhook event.
Figure 18: Receive Appointment-Changed Events via Webhook
required | object (Load and Stop Identifiers) All known identifiers associated with the stop |
required | object The current state of the appointment at the time of the event |
object The prior state of the appointment before the event | |
reasonCode | string Reason for the appointment change. Reason codes are unstructured and TMS-specific. |
comment | string Additional comment associated with the appointment change event. Comments are unstructured and TMS-specific. |
occurredAt required | string <date-time> Time at which the event occurred |
This example is the bare minimum required for the appointment-changed-event
{- "identifiers": {
- "primaryReferenceNumber": "LOAD1234",
- "stopSequenceNumber": 1
}, - "currentAppointment": {
- "appointment": {
- "id": "1234",
- "arrivalWindow": {
- "startDateTime": "2023-04-15T12:00:00.000-05:00",
- "duration": "PT1H"
}, - "loadingType": "LIVE"
}, - "appointmentStatus": "CONFIRMED"
}, - "occurredAt": "2023-04-15T08:02:23.123Z"
}
As a TMS / appointment scheduling solution, I expect to send asynchronous information about available appointments to carriers / brokers who are subscribed to the "fetch-available-appointments" webhook event.
Figure 19: Receive Fetch-Available-Appointments Events via Webhook
status required | string Value: "SUCCESS" Status indicating that the request was successful. |
required | Array of objects (Available Appointment) Collection of available appointments |
required | object (requestIdentifier) All known identifiers associated with the stop |
occurredAt required | string <date-time> Time at which the event occurred |
Example of a success response
{- "status": "SUCCESS",
- "appointments": [
- {
- "id": "1234",
- "appointmentType": "AUTOMATIC",
- "arrivalWindow": {
- "startDateTime": "2023-04-15T12:00:00.000-05:00",
- "duration": "PT1H"
}, - "loadingType": "LIVE"
}
], - "identifiers": {
- "requestIdentifier": "R1234"
}, - "occurredAt": "2023-04-15T08:02:23.123Z"
}