Important concepts can be found in this section as well as detailed information on the API endpoints.
All URL paths referenced in this document are relative to the following base URL
API | Base URL |
---|---|
PFLlink API | https://pflapi.com/api/v1 |
The PFLlink API exposes the following objects. In general, all objects are contextualized to the User identified by the Bearer token.
For full details of the REST endpoints and their supported operations, see the API Reference at the PFLlink Developer Portal
NOTE:
You must be a registered developer and subscribed to the API to send requests through the interactive API test console.
To view the PFLlink API using the Swagger UI explorer, visit http://petstore.swagger.io/?url=https://pflapi.com/meta/v1/swagger
The Swagger UI explorer offers better documentation on the request and response models used by the API, but it doesn’t allow for you to test calls against the API at this time.
The (production) Swagger 2.0 metadata file can be downloaded from the following URL:
A handful of the API requests return a subset of available object data by default. By passing an optional GET parameter you can request that full object details be returned. To get full object details pass a GET parameter with a key of ‘verbose’ and a value of ‘true’.
By default, this is set to ‘false’. In general, the default response is enough to generate a user interface but there may be cases where more information is needed. Be aware that setting the verbose flag to ‘true’ will cause the response to be much slower. Use this feature only where appropriate.
The verbose flag is available on the following functions :
- GET /application
- GET /store (only StoreID is returned by default)
- GET /store/{StoreID}/product
This table provides a list of the status codes that can be returned on API calls and a brief description of each.
Code | Response | Notes |
---|---|---|
200 | OK | Success state that returns a response payload. If there is no content associated with the response, a 204 status code will typically be returned instead. |
201 | Created | Success state that generally returns a populated ID in addition to the original information entered. |
204 | No Content | Success state that does not have a payload. |
400 | Bad Request | Failure state that does return a response body. (Details of what is returned are shown below.) |
401 | Unauthorized (Invalid) | Failure state that does not return a response body. This code is typically returned due to a missing auth token, but can also be returned if access for that particular request is not allowed. |
404 | Does not exist or is not available | Standard ‘Could Not Be Found’ failure state. This does not return a response body. |
400 Status Code Response Details
{
"Message": "The request is invalid.",
"ModelState": {
"application.Name": ["The Name field is required."]
}
}
\\ The model state returned in a 400 error can have multiple properties and multiple failures per property.
Applications can be either first-party integrations (with applications like Marketo or ExactTarget) or custom applications created in the PFL Portal. Applications generally grant users access to their PFL stores and products from the perspective of a marketing campaign. Events in an application can be used to trigger specific, targeted orders.
Operation | HTTP Method | Path | Description |
---|---|---|---|
Icon | GET | /application/{applicationID}/icon | Gets the icon of an application |
Set Icon | POST | /application/{applicationID}/icon | Sets an icon for the application |
Create | POST | /application/{applicationID}/key | Creates a new key for an existing application |
Regenerate Key | PATCH | /application/{applicationID}/key/{keyID}/regenerate | Creates a new key based off an existing one |
Remove Key | DELETE | /application/{applicationID}/key/{keyID} | Deletes an existing key |
Read List | GET | /application/{applicationID}/user | Returns either all user roles associated with the specified app or only the roles associated with the user, depending on permissions |
Read List | GET | /application/{applicationID}/user/{userID} | With sufficient permissions, this returns all roles associated with the specified app and user otherwise it returns access denied |
Associate to User | POST | /application/{applicationID}/user | Associates a user to an application and grants them a specified role |
Remove Permissions | DELETE | /application/{applicationID}/user | Removes all permissions for the current user, however, the ‘admin’ privilege can only be removed if another user has been granted admin rights |
Remove Permissions | DELETE | /application/{applicationID}/user/{userID} | Removes all permissions for the specified user, however, the ‘admin’ privilege can only be removed if another user has been granted admin |
Remove Permission | DELETE | /application/{applicationID}/user/{userID}/role/{roleID} | Removes a single permission for the specified user |
Associate to Store | POST | /application/{applicationID}/store | Associates an application with a store |
Remove Association | DELETE | /application/{applicationID}/store/{storeID} | Removes an association from an application to a store |
Read List | GET | /application/{applicationID}/store | Gets all stores associated with an application |
Update | PATCH | /application/{applicationID} | Updates an existing application |
Create | POST | /application | Createa a new application. The current user will get ‘admin’ for the newly created application automatically. |
Read List | GET | /application | Lists all applications that the current user has access to |
Read Details | GET | /application/{applicationID} | Returns complete details for a single application |
Enable/Disable | DELETE | /application/{applicationID} | Enables or disables an application |
The EDDM delivery endpoints return data on the number of residents and businesses within a certain postal area (population details).
Operation | HTTP Method | Path | Description |
---|---|---|---|
Read List | GET | /delivery/eddm | Fetches the population for a set of postal codes. The list of postal codes cannot exceed 100 or a 404 error will be returned. |
Read Detail | GET | /delivery/eddm/{postalCode} | Fetches the population for a specific postal code |
Read Detail | GET | /delivery/eddm/{postalCode}/route/{carrierRouteID} | Fetches the population of a specific route |
The meta collection of endpoints provides information about the different lookup types/enums used by the PFLlink API.
Operation | HTTP Method | Path | Description |
---|---|---|---|
Read List | GET | /meta/permissions | Fetches a list of acceptable permissions and special roles |
Read Detail | GET | /meta/permissions/{special} | Returns the underlying permissions associated with a special role name |
Read List | GET | /meta/orderstatus | Fetches a list of order statuses |
Read List | GET | /meta/paymentMethod | Returns a list available payment methods |
Read Detail | GET | /meta/about | Returns version information about the API |
When a Product is sent, an Order is created. As the Order moves through the fulfillment lifecycle, the Order object is updated with the current status and relevant tracking numbers. The order endpoints also contain information on the user who placed the order, who the recipient is, and payment details. (Order Status events can optionally be broadcast out to a configurable endpoint. See Order Status Events.)
Operation | HTTP Method | Path | Description |
---|---|---|---|
Price Quote | POST | /store/{storeID}/order/price | Get a price quote for a batch (or single) order |
Status | GET | /store/{storeID}/order/{orderID}/status | Get order status history for a single order |
Read List | GET | /store/{storeID}/order | Get a list of orders that have been submitted by the current user |
Status | GET | /store/{storeID}/order/{orderID} | Get a single order status |
Create | POST | /store/{storeID}/order | Create a batch (or single) order |
Update | PATCH | /store/{storeID}/order/{orderID} | Update an existing order that is in a recoverable (3000) status. Can only be used when the order is in this state. Null fields in the request will be replaced with their prior values |
Below is an example of creating a minimal order.
REQUEST
Method: POST
URL: APIRoot/store/123456/order
{
"Items": [
{
"Products": [{"TemplateFields":{},"DeliveryMethod":"FDXG","ID":"9876","Quantity":1}],
"Recipients": [{
"Email": "Regression.Malcolm@45n.co",
"Title": "Captain",
"NameFirst": "Malcolm",
"NameLast": "Reynolds",
"CompanyName": "Firefly Enterprises",
"AddressLine1": "100 Junker Lane",
"AddressLine2": "",
"AddressLine3": "",
"City": "Standardville",
"Province": "MT",
"Postalcode": "59718",
"Country": "US",
"BusinessPhone": "406-555-5555",
"MobilePhone": ""
}]
}
],
"MaxRetries": 1,
"TestMode": true
}
RESPONSE
Status Code: 200
[{
"ID": "91ff4512-8fa7-42d7-a472-cbdccea8a35b",
"StatusDescription": "Order Request Received",
"StatusID": 1000,
"Completed": false,
"OrderNumber": null,
"StoreID": "123456",
"Created": "2016-09-22T15:55:03.8738708Z",
"Updated": null,
"OrderDetails": null,
"_meta": {
"Messages": []
}
}]
Below is a request for order details containing high level information about what status it is and what the order that created it looked like.
REQUEST
Method: GET
URL: APIRoot/store/storeID/order/orderID
RESPONSE
Status Code: 200
{
"ID": "*Order ID*",
"StatusDescription": "Order Placed",
"StatusID": 2000,
"Completed": false,
"OrderNumber": "*Order Number*",
"StoreID": "*Store ID*",
"Created": "2016-09-27T20:11:22.143",
"Updated": null,
"OrderDetails": {
"Products": [{
"DeliveryMethod": "FDXG",
"ID": "9895",
"Quantity": 1
}],
"Recipient": {
"Email": "Regression.Malcolm@45n.co",
"Title": "Captain",
"NameFirst": "Malcolm",
"NameLast": "Reynolds",
"CompanyName": "Firefly Enterprises",
"AddressLine1": "100 Junker Lane",
"City": "Standardville",
"Province": "MT",
"Postalcode": "59718",
"Country": "US",
"BusinessPhone": "406-624-9655",
"MobilePhone": ""
}
},
"_meta": {
"Messages": []
}
}
Below is an example of retrieving order status history.
REQUEST
Method: GET
URL: APIRoot/store/storeID/order/orderID/status (use ?latest=true to get just the most recent status)
RESPONSE
Status Code: 200
[{
"ProductID": "9895",
"OrderID": "ORDER ID",
"OrderNumber": "B11908540651",
"Timestamp": "2016-09-27T20:12:22.603",
"StatusID": 2000,
"StatusDescription": "Order Placed",
"_meta": {
"Messages": ["New Order Placed: B11908540651"]
}
},
{
"OrderID": "32262355-de3d-42af-9f48-b1b8ddb46ddb",
"OrderNumber": "B11908540651",
"Timestamp": "2016-09-27T20:11:22.143",
"StatusID": 1000,
"StatusDescription": "Order Request Received"
}]
The order endpoint payloads can contain billing variables. Billing variables are simply a key / value pair that will be added to the PFL order. Think of them as user-specified meta data; for example, a user might want to include the static value 'Marketing Department : Q1 Budget' on their invoices.
Requirements
Below is the payload for the GET /order endpoint for reference on how billing variables are formatted.
{
"PageIndex": 0,
"PageSize": 0,
"TotalCount": 0,
"TotalPageCount": 0,
"HasNextPage": true,
"HasPrevPage": true,
"Items": [
{
"PartnerOrderReference": "string",
"ID": "string",
"StatusDescription": "string",
"StatusID": 0,
"Completed": true,
"OrderNumber": "string",
"StoreID": "string",
"Created": "2016-08-18T16:27:22.723Z",
"Updated": "2016-08-18T16:27:22.723Z",
"OrderDetails": {
"Product": {
"TemplateFields": {},
"DeliveryMethod": "string",
"IntelligentMailingSerialNumber": "string",
"ID": "string",
"Quantity": 0,
"FileURL": "string",
"ProductionSpeedDays": 0
},
"Products": [
{
"TemplateFields": {},
"DeliveryMethod": "string",
"IntelligentMailingSerialNumber": "string",
"ID": "string",
"Quantity": 0,
"FileURL": "string",
"ProductionSpeedDays": 0
}
],
"Recipient": {
"Email": "string",
"Title": "string",
"NameFirst": "string",
"NameLast": "string",
"CompanyName": "string",
"AddressLine1": "string",
"AddressLine2": "string",
"AddressLine3": "string",
"City": "string",
"Province": "string",
"Postalcode": "string",
"Country": "string",
"BusinessPhone": "string",
"MobilePhone": "string"
},
"EddmDeliveryAreas": [
{
"PostalCode": "string",
"CarrierRoute": "string",
"TargetAudience": "All"
}
],
"Customer": {
"NameFirst": "string",
"NameLast": "string",
"CompanyName": "string",
"AddressLine1": "string",
"AddressLine2": "string",
"City": "string",
"Province": "string",
"PostalCode": "string",
"Country": "string",
"Email": "string",
"Phone": "string"
},
"Payments": [
{
"ExpectedAmount": 0,
"ID": "string",
"Method": "stripe"
}
],
"PartnerOrderReference": "string",
"StatusCallbackUrl": "string",
"StatusDetailsCallbackUrl": "string",
"BillingVariables": [
{
"key": "string",
"value": "string"
}
]
},
"_meta": {
"Messages": [
"string"
],
"ChangeType": "Created"
}
}
]
}
The GAPI order endpoints support the ability to have multiple recipients per order in which each recipient can receive all or part of the specified products. For example, this functionality makes it possible for 50 people to all get one item or for a certain 25 to get one thing and the other 25 to get another thing (and the 5 magic people in the middle would get both).
Sample Use Cases
Requirements / Notes
Available Shipping Methods
Code | Description |
---|---|
1C | 1st Class |
1CP | 1st Class - Presorted |
BM | Bulk Mail |
Below is a sample payload for the structure of a multiple recipient order. The Quantity field will be computed automatically so it is not recommended that this ever be set manually when using multiple-recipients.
{
"Items": [{
"Products": [{
"DeliveryMethod": "string",
"ID": "asdf",
"Quantity": 302,
"LimitToRecipients": [{
"RecipientKey": "1234",
"Quantity": 2
}, {
"RecipientKey": "5678",
"Quantity": 300
}]
}, {
"DeliveryMethod": "string",
"ID": "xkcd",
"Quantity": 2,
"LimitToRecipients": [{
"RecipientKey": "1234",
"Quantity": 1
}, {
"RecipientKey": "5678",
"Quantity": 1
}]
}, {
"DeliveryMethod": "USPS",
"ID": "qwert",
"Quantity": 3,
"QuantityPerRecipient": 1
}],
"Recipients": [{
"RecipientKey": "5678",
"NameFirst": "string",
"NameLast": "string",
"AddressLine1": "string"
}, {
"RecipientKey": "1234",
"NameFirst": "string",
"NameLast": "string",
"AddressLine1": "string"
}, {
"NameFirst": "string",
"NameLast": "string",
"AddressLine1": "string"
}],
}],
"MaxRetries": 0,
"TestMode": true
}
The POST /store/{storeID}/order endpoint allows a customer to pay for an order with a credit card by including a Payments property in the payload. One important nuance of the Payments property is that it contains an ExpectedAmount field. The ExpectedAmount field acts as a sanity check for orders that may have a different negotiated price than final price. It is possible to leave this field empty and have the value default to the complete order value. It does not effect what is actually charged. Any value entered in the ExpectedAmount field will be cross-checked against the cost of the order. If a discrepancy between the two is found, it will halt the order until the customer and PFL resolve the different.
The Payment property can be added to the POST /store/{storeID}/order endpoint to allow the customer to pay using a credit card. It is an optional property. A sample payload that includes the Payments property is shown below. Adding the Payments property with the appropriate values will allow the credit card information to be processed and charged.
{
"Items": [
{
"Product": {
"TemplateFields": {},
"DeliveryMethod": "string",
"ID": "string",
"Quantity": 0,
"FileURL": "string"
},
"Products": [
{
"TemplateFields": {},
"DeliveryMethod": "string",
"ID": "string",
"Quantity": 0,
"FileURL": "string"
}
],
"Recipient": {
"Email": "string",
"Title": "string",
"NameFirst": "string",
"NameLast": "string",
"CompanyName": "string",
"AddressLine1": "string",
"AddressLine2": "string",
"AddressLine3": "string",
"City": "string",
"Province": "string",
"Postalcode": "string",
"Country": "string",
"BusinessPhone": "string",
"MobilePhone": "string"
},
"Payments": [
{
"ExpectedAmount": 0,
"ID": "string",
"Method": "stripe"
}
],
"PartnerOrderReference": "string",
"StatusCallbackUrl": "string"
}
],
"MaxRetries": 0,
"TestMode": true
}
The following notes contain details on the three fields included in the Payments property and their particular quirks:
ExpectedAmount
This is not a required field and should only be used if there is a chance that the negotiated value of the order and the actual price of the order may differ (as may be the case when the actual order size is ambivalent). Adding a value here will cross-check this value against the calculated actual cost of the order. If the two are the same, the order will proceed as expected. If the two differ, the order will be halted until the customer and PFL have a chance to sort out the difference. If no value is entered, it will default to null and the order will proceed as expected, using the cost of the order as the amount to be charged to the credit card.
ID
The ID is a single use token returned by the Stripe API. This is not the credit card number. More details on how the ID is generated from Stripe can be found here.
Method
PFL uses Stripe APIs for credit card payments. Information on the how the Stripe APIs work can be found here and it is important to note that the Payment property currently only accepts the Stripe Production White Label public token.
As an order goes through all its stages from beginning to end, there are various status events that indicate its progress.
1000 : Order Request Recieved |
---|
A 1000 status code indicates that the order request has been successfully received by PrintingForLess and is being processed. An OrderID is generated that can be used to reference the order. At this point the OrderNumber does not yet exist. (Refer to the table at the bottom of this page for more details on OrderID and OrderNumber.) |
From here, the order will either progress to a 2000 status (indicating the order was successfully placed), a 3000 status (indicating there was an error with the order’s information) or a 5000 status (indicating the order request failed for an unknown reason). Common errors that would trigger a 3000 status are an incorrect shipping method––like specifying FedEx details for an order that is set up to be shipped through the Postal Service––or an invalid quantity ––like a negative number or ordering more than the available number of products. |
2000 : Order Placed |
---|
A 2000 status code indicates that the order has been successfully processed by PrintingForLess and is now in progress. From here, the order will sequentially progress through the remaining stages of production and shipping until the customer has received the product(s) unless someone cancels the order. |
There is a callback URL with each order that can potentially result in communication failures between the PFL API and the associated application. This simply means that it is possible for the order to successfully go to the next stage of production or delivery but not correctly in the application. |
If order status updates stop showing up in the client application, it is likely this occurred. Automatic retries to communicate the correct status to the application occur in 60 minute increments and will retry 3 times. Any callback communication failures between the PFL API and the application can be viewed in the order history. |
2100 : Order Entered Production |
---|
A 2100 status code indicates the order is actively being printed, packaged or prepared for shipping. |
2200 : Order Shipped |
---|
A 2200 status code indicates the order has shipped and (if a tracking number has been provided) can be monitored. Depending on the carrier, it is possible for this to be the final status of the order. |
2300 : Order Delivered |
---|
A 2300 status code indicates the order has been successfully delivered. This is likely to be the final status of the order, although some deliveries will also include a ‘picked up’ status (2400). |
2400 : Order Picked Up |
---|
A 2400 status code indicates the order has been successfully picked up. This would typically apply in a situation where the recipient has to retrieve the order from a location like a FedEx center. This is the final status of the order. |
2500 : Order Cancelled |
---|
A 2500 status code indicates that PrintingForLess has cancelled the order. In all likelihood this event was triggered by a customer placing a phone call to PFL stating they would like to cancel it. Details about when the order was cancelled will be available in the order history. |
3000 : Recoverable Error Occurred |
---|
A 3000 status code indicates that something went wrong when the order was placed and it was rejected due to an error in the order’s information. The status history will contain appropriate information on what caused the failure. Common mistakes to look for are incorrect shipping methods––like specifying FedEx details for an order that is set up to be shipped through the Postal Service––or invalid quantities––like a negative number or ordering more than the available number of products. |
5000 : Failed to Place Order |
---|
A 5000 status indicates a critical failure occurred during the processing stage. Resubmit the order. |
5300 : Failed to Deliver Order |
---|
In the unlikely event that an order failed to deliver, the carrier may have details about what went wrong. |
Products are contained inside of a Store. Products are sendable items. Some printed products contain variable data that can be supplied at order time; examples include a personalized salutation on a brochure or postcard.
Operation | HTTP Method | Path | Description |
---|---|---|---|
Create | POST | /store/{storeID}/product/{productID}/proof | Generate proof URLs for products with template data |
Read List | GET | /store/{storeID}/product | Get all products for the specified store |
Read Detail | GET | /store/{storeID}/product/{productID} | Get a single product from the specified store. This always returns the full (non-sparse) details |
Below is an example of a request for available products.
REQUEST
Method: GET
URL: APIRoot/store/storeID/product (use ?verbose=true to get template field values)
RESPONSE
Status Code: 200
[{
"StoreID": "*STORE ID*",
"ID": "9876",
"DefaultQty": 1,
"MinimumQty": 1,
"MaximumQty": null,
"IncrementQty": 1,
"Name": "Postcards 4x6 Color/Color",
"Description": "",
"ImageURL": "//sw-public.pflnet.net/tosthumbnails/lobapi/lob_postcard.jpg",
"HasTemplate": false,
"DeliveryMethod": "FDXG",
"TemplateFields": null,
"DeliveredPrices": [{
"DeliveryMethodCode": "FDXG",
"Description": "FedEx Ground",
"Price": 0.97,
"Country": null,
"CountryCode": null,
"Created": "2016-09-23T11:32:28.85",
"LocationType": "domestic",
"IsDefault": true
},
{
"DeliveryMethodCode": "1C",
"Description": "1st Class",
"Price": 26.060000000000002,
"Country": null,
"CountryCode": null,
"Created": "2016-09-23T11:32:28.867",
"LocationType": "domestic",
"IsDefault": false
}],
"ProductionSpeeds": [{
"Days": 1,
"IsDefault": true
}]
},
{
"StoreID": "*STORE ID*",
"ID": "9877",
"DefaultQty": 1,
"MinimumQty": 1,
"MaximumQty": null,
"IncrementQty": 1,
"Name": "Postcards 5x7 Color/Color",
"Description": "",
"ImageURL": "//sw-public.pflnet.net/tosthumbnails/pflautomation/testhangtags.jpg",
"HasTemplate": false,
"DeliveryMethod": "FDXG",
"TemplateFields": null,
"DeliveredPrices": [{
"DeliveryMethodCode": "FDXG",
"Description": "FedEx Ground",
"Price": 1.23,
"Country": null,
"CountryCode": null,
"Created": "2016-09-23T11:32:28.867",
"LocationType": "domestic",
"IsDefault": true
},
{
"DeliveryMethodCode": "1C",
"Description": "1st Class",
"Price": 26.32,
"Country": null,
"CountryCode": null,
"Created": "2016-09-23T11:32:28.867",
"LocationType": "domestic",
"IsDefault": false
}],
"ProductionSpeeds": [{
"Days": 1,
"IsDefault": true}]
}]
There is one default domestic shipping method and one default international shipping method. Although that's the case and the expected behavior might be a single entry returned for each, it is possible for the API to return multiple entries for any international shipping method. Multiple entries are returned when there is more than one country because it is possible for the price to vary by country. In addition, a null entry will always be returned per type of shipping method (both international and domestic). As you can see in the code below, the five highlighted entries with are identical (i.e. the same default international shipping method) with their only variation occurring in the country and country code returned. The entries that are not highlighted are a shipping method that is not the default, but are also all the same type, Economy.
{
"DeliveryMethodCode": "FDXIP",
"Description": "FedEx International Priority",
"Price": 0.0,
"Country": null,
"CountryCode": null,
"Created": "2016-02-08T14:34:50.067",
"LocationType": "international",
"IsDefault": true
},
{
"DeliveryMethodCode": "FDXIP",
"Description": "FedEx International Priority",
"Price": 0.0,
"Country": "Australia",
"CountryCode": "AU",
"Created": "2016-02-08T14:34:50.067",
"LocationType": "international",
"IsDefault": true
},
{
"DeliveryMethodCode": "FDXIP",
"Description": "FedEx International Priority",
"Price": 0.0,
"Country": "Canada",
"CountryCode": "CA",
"Created": "2016-02-08T14:34:50.067",
"LocationType": "international",
"IsDefault": true
},
{
"DeliveryMethodCode": "FDXIP",
"Description": "FedEx International Priority",
"Price": 0.0,
"Country": "Netherlands",
"CountryCode": "NL",
"Created": "2016-02-08T14:34:50.067",
"LocationType": "international",
"IsDefault": true
},
{
"DeliveryMethodCode": "FDXIP",
"Description": "FedEx International Priority",
"Price": 0.0,
"Country": "Norway",
"CountryCode": "NO",
"Created": "2016-02-08T14:34:50.067",
"LocationType": "international",
"IsDefault": true
},
{
"DeliveryMethodCode": "FDXIE",
"Description": "FedEx International Economy",
"Price": 0.0,
"Country": null,
"CountryCode": null,
"Created": "2016-02-08T14:34:50.067",
"LocationType": "international",
"IsDefault": false
},
{
"DeliveryMethodCode": "FDXIE",
"Description": "FedEx International Economy",
"Price": 0.0,
"Country": "Australia",
"CountryCode": "AU",
"Created": "2016-02-08T14:34:50.067",
"LocationType": "international",
"IsDefault": false
},
{
"DeliveryMethodCode": "FDXIE",
"Description": "FedEx International Economy",
"Price": 0.0,
"Country": "Canada",
"CountryCode": "CA",
"Created": "2016-02-08T14:34:50.067",
"LocationType": "international",
"IsDefault": false
},
{
"DeliveryMethodCode": "FDXIE",
"Description": "FedEx International Economy",
"Price": 0.0,
"Country": "Netherlands",
"CountryCode": "NL",
"Created": "2016-02-08T14:34:50.067",
"LocationType": "international",
"IsDefault": false
},
{
"DeliveryMethodCode": "FDXIE",
"Description": "FedEx International Economy",
"Price": 0.0,
"Country": "Norway",
"CountryCode": "NO",
"Created": "2016-02-08T14:34:50.067",
"LocationType": "international",
"IsDefault": false
}
Some products can be configured to include template fields. These fields need to have populated values when an order is submitted. To check if a product has a template field look for "HasTemplate": true
in the response from GET /store/{id}/product
Products that have templates will return an array of template fields:
"TemplateFields": [
{
"Required": false,
"Type": "SINGLELINE",
"SubType": "A",
"FieldName": "FIRST_NAME",
"Prompt": "First Name",
"DefaultValue": "",
"OrgValue": "PFL"
},
{
"Required": false,
"Type": "SINGLELINE",
"SubType": "A",
"FieldName": "EVENT_NAME",
"Prompt": "Event Name",
"DefaultValue": "",
"OrgValue": "Resource Celebration"
},
{
"Required": false,
"Type": "SINGLELINE",
"SubType": "A",
"FieldName": "SALESPERSON",
"Prompt": "Salesperson",
"DefaultValue": "",
"OrgValue": "Seth"
}
],
The contents of each field should provide enough information to create a useful user interface for the field
Template Field Types indicate the type of user interface that should be used to gather the data from the user. The types are (with indented subtypes):
Values for template fields are included in an Order by populating a Key/Value pair in the product object on the request. The Key is the FieldName value from the template definition and the value is the data gathered from the user:
{
"Items": [{
"Product": {
"TemplateFields": {
"FieldName": "Value"
},
"DeliveryMethod": "string",
"ID": "string",
"Quantity": "0"
}
...
}]
}
Passing in key=value pairs of template fields will return a rendered versions of the product via a URL. This only works for products that have template fields. Additionally, the template cannot be completely blank (which would hopefully be an invalid use case anyway); at least one of the template fields must contain data.
{
"FIRST_NAME": "John"
"SALESPERSON": "Jeor Mormont"
"EVENT_NAME": ""
}
The response payload includes the following:
Name | Description | Type |
---|---|---|
ProductID | product ID | string |
Side1URL | JPG URL for the first side (front) | string |
Side2URL | JPG URL for the second side (back) | string |
PdfURL | PDF URL for the entire document | string |
PageURLs | JPG URLs for each page in the document | collection of strings |
The stats collection of endpoints provides additional details on the user and their store interactions.
Operation | HTTP Method | Path | Description |
---|---|---|---|
Read Detail | GET | /stats | Gets stats about the current user |
Read Detail | GET | /stats/store/{storeID} | Gets stats about store orders for the current user and the specified store |
A Store represents a collection of products that can be sent through the PFLlink API. Stores contain budgets, products and orders.
Operation | HTTP Method | Path | Description |
---|---|---|---|
Associate to Store | POST | /store/invite | Associate an existing user with a new PFL store |
Read List | GET | /store/{storeID}/user | Depending on permission level, this returns either a list of all user roles associated with the store or a list of the current user’s roles |
Read Detail | GET | /store/{storeID}/user/{userID} | Depending on permission level, this returns either details on other users of the store or details on the current user only |
Grant Role | POST | /store/{storeID}/user | Associate a user to a store and grant them a specified role |
Remove Permissions | DELETE | /store/{storeID}/user | Remove all permissions for the current user. Cannot be used if the current user is in the only admin role |
Remove Permissions | DELETE | /store/{storeID}/user/{userID} | Remove all permissions for a specified user. Cannot be used if the specified user is in the only admin role |
Remove Permission | DELETE | /store/{storeID}/user/{userID}/role/{roleID} | Removes a single permission for the specified user |
Read List | GET | /store/connectedApps | Fetches a list of non-generic applications available to stores the user is the admin of |
Read All | GET | /store/{storeID}/connectedApps | Finds all stores where the user is admin, then searches for (and returns) any non-generic integrations to that store. All connected apps are returned for PFL users. |
Read List | GET | /store | Returns a list of all accessible stores |
Read Detail | GET | /store/{storeID} | Returns full details of specified store |
Below is an example of fetching a list of available stores.
REQUEST
Method: GET
URL: APIRoot/store (use ?verbose=true to include company names in the response payload)
RESPONSE
Status Code: 200
[{
"StoreID": "140158",
"BudgetEnabled": false,
"BudgetTypes": null,
"BudgetDuration": null,
"CompanyName": null
},
{
"StoreID": "136085",
"BudgetEnabled": false,
"BudgetTypes": null,
"BudgetDuration": null,
"CompanyName": null
}]
A User represents an Identity in the PFLlink Identity Server. A User is associated to one or more Stores
Operation | HTTP Method | Path | Description |
---|---|---|---|
Read | GET | /user/roles | View all roles assigned to the current user |
Read | GET | /user/{userID}/roles | View all roles assigned to the specified user. (Applications cannot use this function.) |
Create | POST | /user | Create a new User in the PFLlink Identity Server. This method does not require a Bearer token |
Enable/Disable | DELETE | /user/{userID} | Change the enablement of a user’s required permissions (only available to PFL employees) |
Read Detail | GET | /user | Get details about the current user |
Read Detail | GET | /user/{userID} | Get details about a specific user |
As a user you can have various levels of access to three things : orders, stores and applications. This section is designed to explain the available user roles and illustrate the level of access they grant. In addition to the packaged permission sets defined below (Default, Report, and Admin) which contain predefined groups of permissions, it is possible to grant an individual permission (or subset of permissions) to a user.
For example, as admin, you may want to grant only store.all.write privileges to the users of a store, allowing any of them to place orders, without giving them full rein to wreak havoc through a complete administrator permissions set. More details and helpful specifics can be found in the administrator description below.
It may be useful to note that the GET meta/permissions functions return information on available permissions and special roles.
Default (none) Permissions Set
The default (none) permissions set grants you a basic set of privileges to view orders you have placed and stores and applications you have access to. With this permission set, you also have the ability to create applications. (When you create an application, you are automatically the admin with global read / write privileges.)
Report Permissions Set
The report permissions set is similar to the default permissions set but, in addition, allows you to view details of all users of a store or application, all user roles associated with a particular store, and all orders that have been placed in your store(s).
Administrator Permissions Set
The administrator permissions set grants you global read / write access to the API endpoints. Additionally, this role can be used to assign a specific permission to a user without giving them full admin rights.
A detailed table of the specific permissions required to use each function (and conversely, the functions that each permission gives access to) can be found here. This section, in particular, will serve as your guide in determining which particular permission(s) to grant on an as-needed basis.
If you create an application, you are automatically the admin. If you are the first user granted access to a store via an invite key, you are, likewise, automatically admin. It is worth noting that a user cannot be removed from a store or an application if they are its only admin. Think of the powerlessness that would ensue.
The following table lists which user roles have access to each function.
The default permissions set and the report permissions set grant access to the same set of functions. As mentioned in the descriptions, the defining difference is that for a subset of those functions, the report set grants global read access, whereas the default set only provides details on the current user. Where the report permissions set provides a wider set of information, it has been flagged with an asterisk (*). Details on the exact information returned are available by clicking the URL.
* Delineates that for this function, the report permissions set returns a wider set of information than the default permissions set. Details on the exact information returned are available by clicking the URL.
The administrator role contains this entire list of permissions. (The default and report permissions sets (roles) each contain a specific subset of these.) The administrator can dole specific permissions from this list out as needed to grant users access to the functionality available in particular endpoints.
For example, an admin can grant one of the store permissions to a user by using the POST /store/{storeID}/user function. Similarly, the admin can grant one of the application permissions to a user with the POST /application/{applicationID}/user function.
All Permissions
store.all.read
store.all.write
store.all.delete
store.self.read
store.self.write
store.self.delete
order.all.read
order.all.write
order.all.delete
order.self.read
order.self.write
order.self.delete
application.all.read
application.all.write
application.all.delete
application.self.read
application.self.write
application.self.delete
Function | URL | Required Permissions |
---|---|---|
GET | /application/{applicationID}/icon | none : as long as an association exists between the user and app* |
POST | /application/{applicationID}/icon | application.all.write or application.self.write |
POST | /application/{applicationID}/key | application.all.write or application.self.write |
PATCH | /application/{applicationID}/key/{keyID}/regenerate | application.all.write or application.self.write |
DELETE | /application/{applicationID}/key/{keyID} | application.all.write or application.self.write |
DELETE | /application/{applicationID}/user | application.all.write or application.self.write Not allowed if the user to be removed is the only admin |
GET | /application/{applicationID}/user | application.all.read or application.self.read : Global details are returned for .all access. User specific details for .self access. |
POST | /application/{applicationID}/user | application.all.write or application.self.write |
DELETE | /application/{applicationID}/user/{userID} | application.all.write or application.self.write |
GET | /application/{applicationID}/user/{userID} | application.all.read or application.self.read : Global details are returned for .all access. User specific details for .self access. |
DELETE | /application/{applicationID}/user/{userID}/role/{roleID} | application.all.write or application.self.write Not allowed if the user to be removed is the only admin |
GET | /application/{applicationID}/store | application.all.write or application.self.write and association with the specified store |
POST | /application/{applicationID}/store | application.all.write or application.self.write and store.all.write or store.self.write |
DELETE | /application/{applicationID}/store/{storeID} | application.all.write or application.self.write |
DELETE | /application/{applicationID} | application.all.delete or application.self.delete : Only case in which the .delete permission is required |
GET | /application/{applicationID} | none : as long as an association exists between the user and app* |
PATCH | /application/{applicationID} | application.all.write or application.self.write |
GET | /application | none : as long as an association exists between the user and app* |
GET | /delivery/eddm | none |
GET | /delivery/eddm/{postalCode} | none |
GET | /delivery/eddm/{postalCode}/route/{carrierRouteID} | none |
GET | /meta/about | N/A |
GET | /meta/permissions | N/A |
GET | /meta/permissions/{special} | N/A |
GET | /meta/orderstatus | N/A |
GET | /meta/paymentMethod | N/A |
GET | /stats | none |
GET | /stats/store/{storeID} | store.all.read or store.self.read |
POST | /store/{storeID}/order/price | order.all.write or order.self.write |
GET | /store/{storeID}/order/{orderID}/status | order.all.read or order.self.read : Global details are returned for .all access. User specific details for .self access. |
GET | /store/{storeID}/order | order.all.read or order.self.read and association with the specified store : Global details are returned for .all access. User specific details for .self access. |
POST | /store/{storeID}/order | order.all.write or order.self.write |
GET | /store/{storeID}/order/{orderID} | order.all.read or order.self.read and association with the specified store : Global details are returned for .all access. User specific details for .self access. |
PATCH | /store/{storeID}/order/{orderID} | order.all.write or order.self.write and association with the specified store : Global details are returned for .all access. User specific details for .self access. |
GET | /store/{storeID}/product | none : as long as an association exists between the user and app* |
GET | /store/{storeID}/product/{productID} | none : as long as an association exists between the user and app* |
POST | /store/{storeID}/product/{productID}/proof | none : as long as an association exists between the user and app* |
POST | /store/invite | none |
DELETE | /store/{storeID}/user | store.all.write : store.self.write only allows you to remove yourself from the Demo store. Never allowed if the user to be removed is the only admin |
GET | /store/{storeID}/user | store.all.read or store.self.read |
POST | /store/{storeID}/user | store.all.write |
DELETE | /store/{storeID}/user/{userID} | store.all.write : store.self.write only allows you to remove yourself from the Demo store. Never allowed if the user to be removed is the only admin |
GET | /store/{storeID}/user/{userID} | store.all.read or store.self.read : Global details are returned for .all access. User specific details for .self access. |
DELETE | /store/{storeID}/user/{userID}/role/{roleID} | store.all.write |
GET | /store/connectedApps | must be store admin |
GET | /store/{storeID}/connectedApps | must be store admin |
GET | /store | none |
GET | /store/{storeID} | none : Limited to stores you can access |
GET | /user/roles | none |
GET | /user | none |
POST | /user | none |
GET | /user/{userID} | none |
* If no association exists between the two, you can request than an admin create the relationship.
Applications only need to be associated to a store if the application is going to authenticating (using client credentials rather than user credentials). In that case the user who is configuring the application must have application.*.write and store.write.
Default Permissions Set
GET /application/{applicationID}/icon
GET /application/{applicationID}/user
GET /application/{applicationID}/user/{userID}
GET /application/{applicationID}
GET /application
POST /application
GET /delivery/eddm
GET /delivery/eddm/{postalCode}
GET /delivery/eddm/{postalCode}/route/{carrierRouteID}
GET /stats
GET /stats/store/{storeID}
GET /store/{storeID}/order/{orderID}/status
GET /store/{storeID}/order
GET /store/{storeID}/order/{orderID}
GET /store/{storeID}/product
GET /store/{storeID}/product/{productID}
POST /store/{storeID}/product/{productID}/proof
POST /store/invite
GET /store/{storeID}/user
GET /store/{storeID}/user/{userID}
GET /store
GET /store/{storeID}
GET /user/roles
GET /user
POST /user
GET /user/{userID}
Report Permissions Set
GET /application/{applicationID}/user
GET /application/{applicationID}/user/{userID}
GET /store/{storeID}/order/{orderID}/status
GET /store/{storeID}/order
GET /store/{storeID}/order/{orderID}
GET /store/{storeID}/user
GET /store/{storeID}/user/{userID}