This is the full developer documentation for DPD
# DPD developer docs
> Put DPD's cart on your own site, style it, and read your store's data with the DPD API.
These docs are for developers and designers building on DPD. If you’re setting up a store, products or payments, the [DPD helpdesk](https://getdpd.com) is the place to start.
## Embed components
[Section titled “Embed components”](#embed-components)
Paste product cards, add to cart buttons and a cart into any page on your site. Your visitors shop on your page, then pay at your store’s hosted checkout. Style them with CSS to match your site.
* [Quick start](components/)
* [Styling](components/styling/)
* [Where they work](components/embed-domains/)
## Classic buy buttons
[Section titled “Classic buy buttons”](#classic-buy-buttons)
DPD’s older embed codes: cart links, buttons, widgets and price choosers that open the cart in a lightbox. They still work, and you can move them to the new cart drawer one link at a time.
* [Buy buttons and links](buttons/)
* [Customizing buttons and widgets](buttons/customize/)
## REST API
[Section titled “REST API”](#rest-api)
The DPD API lets your own code read your account’s storefronts, products, purchases, customers and subscribers. Use it to check a member’s subscription before you show them a page, pull orders into your own reports, or verify the purchase notifications DPD sends your site.
* [Get started with the API](api/)
* [Endpoint reference](api/reference/)
* [The OpenAPI description](openapi.yaml), for code generators and API clients
## For AI agents
[Section titled “For AI agents”](#for-ai-agents)
Building with Claude, Cursor or another coding agent? Point it at [llms.txt](llms.txt) and the [OpenAPI description](openapi.yaml). See [Using DPD with AI agents](ai-agents/).
# Using DPD with AI agents
> Point Claude, ChatGPT, Cursor or another AI agent at DPD's docs and API description.
If you’re building with an AI coding agent, give it these files. They hold the same docs as this site, as plain text an agent can read in one go.
| File | What’s in it |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| [`/llms.txt`](/llms.txt) | An index: what DPD is, the key facts, and links to everything below. Start here. |
| [`/llms-full.txt`](/llms-full.txt) | Every page of these docs in one file. |
| [`/_llms-txt/embed-components.txt`](/_llms-txt/embed-components.txt) | Just the embed components. |
| [`/_llms-txt/classic-buy-buttons.txt`](/_llms-txt/classic-buy-buttons.txt) | Just the classic buy buttons. |
| [`/_llms-txt/rest-api-guide.txt`](/_llms-txt/rest-api-guide.txt) | The REST API guide. |
| [`/openapi.yaml`](/openapi.yaml) | The REST API as OpenAPI 3.1: every endpoint, parameter and response field. |
A prompt like this one works well:
```plaintext
Read https://docs.getdpd.com/llms.txt, then add a product card for DPD
product 163005 and a cart button to the header of my site. My store is
demo.dpdcart.com.
```
For the API, hand it the OpenAPI file. Most agents (and tools like Postman and Insomnia) can build a working client from it:
```plaintext
Using https://docs.getdpd.com/openapi.yaml, write a script that lists
yesterday's purchases from the DPD API. Read the username and API
password from environment variables.
```
Keep your API password out of the chat. Have the agent read it from an environment variable or a secrets file instead.
# Get started
> Authenticate with the DPD API and make your first request.
The DPD API is a read-only window into your DPD account. Your code can read everything your account holds: storefronts, products, purchases, customers and subscribers. It can also [reactivate a purchase](reference/operations/reactivatepurchase/), which emails the customer their downloads again.
Every request goes to this base URL:
```plaintext
https://api.getdpd.com/v2
```
## Authentication
[Section titled “Authentication”](#authentication)
The API uses HTTP Basic authentication. The username is your DPD username, and the password is the **API password** on your profile in the DPD admin (it’s different from the password you sign in with).
Check that your credentials work by pinging the API with curl:
```bash
curl -u YOUR_USERNAME https://api.getdpd.com/v2/
```
curl asks for the password. Paste your API password and you’ll get:
```json
{"status": "SUCCESS"}
```
A wrong username or password gets a `401 Unauthorized`.
## Your first real request
[Section titled “Your first real request”](#your-first-real-request)
List your storefronts:
```bash
curl -u YOUR_USERNAME https://api.getdpd.com/v2/storefronts
```
```json
[
{
"id": 1,
"name": "My Store",
"subdomain": "mystore",
"type": "cart",
"currency": "USD"
}
]
```
(That’s trimmed. Each storefront has a few more fields, listed in [Get a storefront](reference/operations/getstorefront/).)
Most other requests take a storefront or product ID from a response like this one. The [endpoint reference](reference/) lists them all.
## Keep the API password secret
[Section titled “Keep the API password secret”](#keep-the-api-password-secret)
Anyone with your username and API password can read your customers’ names, email addresses and orders. Use it only from your server, never in a web page or an app you hand out, and don’t commit it to a code repository.
# Responses, pages and dates
> How the DPD API pages through lists, formats dates and money, and reports errors.
## Responses
[Section titled “Responses”](#responses)
Responses are JSON. A single record (such as a purchase) comes back as an object, and a list comes back as an array of objects.
Two endpoints answer in plain text instead: [verify a subscriber](../reference/operations/verifysubscriber/) and [verify a purchase notification](../notifications/).
## Status codes
[Section titled “Status codes”](#status-codes)
| Code | Meaning |
| --------------------------- | -------------------------------------------------------------------- |
| `200 OK` | It worked. |
| `401 Unauthorized` | The username or API password is wrong. |
| `403 Forbidden` | You don’t have access to that record. |
| `404 Not Found` | Nothing found. See below. |
| `422 Unprocessable Entity` | The request didn’t pass validation. The body has an `errors` object. |
| `500 Internal Server Error` | Something went wrong on our side. |
| `503 Service Unavailable` | The API is down for maintenance or overloaded. Try again shortly. |
A `404` has this body:
```json
{"status": "NOTFOUND"}
```
You’ll get it when a record doesn’t exist, but also when a list is empty or you ask for a page past the last one. So a search that matches nothing is a `404`, not an empty array.
## Pages
[Section titled “Pages”](#pages)
Lists return 100 records a page when you pass `page`:
```bash
curl -u YOUR_USERNAME "https://api.getdpd.com/v2/purchases?page=2"
```
Leave `page` off and the API tries to return everything at once, which isn’t guaranteed to work on a big account. To fetch a whole list, start at `page=1` and keep counting up until you get a `404`.
## Dates and times
[Section titled “Dates and times”](#dates-and-times)
Dates in responses are UNIX timestamps, in seconds.
Date filters such as `date_min` and `date_max` accept anything PHP’s [`strtotime()`](https://www.php.net/manual/en/function.strtotime.php) does, like `2024-01-01` or `last monday`. They’re read in your account’s time zone unless the value carries its own, as ISO 8601 (`2004-02-12T15:19:21+00:00`) and RFC 2822 (`Thu, 21 Dec 2000 16:01:07 +0200`) dates do.
## Money
[Section titled “Money”](#money)
Amounts are strings with two decimal places, like `"19.99"`, so they don’t lose precision on the way through a JSON parser. The currency is on the storefront or purchase.
# Verify purchase notifications
> Check that a purchase notification POSTed to your site came from DPD.
With the URL integration, DPD POSTs the details of each purchase to a URL on your site. Anyone can POST to that URL, so before you act on a notification (say, by creating an account), check that DPD sent it.
Send every parameter you received back to the API, unchanged, including `verify_sign`. With curl, that’s one `--data-urlencode` for each parameter (only two are shown here):
```bash
curl -u YOUR_USERNAME https://api.getdpd.com/v2/notification/verify \
--data-urlencode "verify_sign=THE_SIGNATURE" \
--data-urlencode "buyer_email=customer@example.com"
```
The answer is plain text, either `VERIFIED` or `INVALID`.
In PHP, forwarding the whole POST looks like this:
```php
$ch = curl_init('https://api.getdpd.com/v2/notification/verify');
curl_setopt_array($ch, [
CURLOPT_USERPWD => 'YOUR_USERNAME:' . getenv('DPD_API_PASSWORD'),
CURLOPT_POSTFIELDS => http_build_query($_POST),
CURLOPT_RETURNTRANSFER => true,
]);
$verified = curl_exec($ch) === 'VERIFIED';
```
# Buy buttons and links
> DPD's classic embed codes, cart links, buttons, widgets and price choosers, and the lightbox they open.
Before the [embed components](../components/), DPD’s embed codes were cart links that open the cart in a lightbox over your page. They still work, and the button code page in the DPD admin still makes them. If you’ve had DPD buttons on your site for years, this is what you’ve got.
Every embed code is an `` tag with the dpd.js script tag after it:
```html
Add to cart
```
You only need the script tag once per page, but pasting it more than once does no harm.
## Kinds of embed code
[Section titled “Kinds of embed code”](#kinds-of-embed-code)
**Cart links.** An `` with your own text or image. dpd.js opens the link in the lightbox.
**Buttons.** An ``, which dpd.js turns into a styled button with the price beside it. See [Buttons and widgets](customize/).
**Widgets.** An ``: a product card with an image, name, price, description and buy button. Also in [Buttons and widgets](customize/).
**Price choosers.** A `