Connect your platform to PCC for seamless labor law poster fulfillment
As a dropship partner, you send orders and we ship directly to your customers under your contracted rates. Here's your simplified flow:
Here's exactly what your checkout request should look like:
POST /v1/checkout HTTP/1.1
Host: api.postercompliance.com
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
{
"account": {
"name": "Acme Corporation",
"shippingAttentionTo": "John Smith",
"locationIdentifier": "CUST-00412",
"email": "john.smith@acme.com",
"phone": "555-123-4567",
"shippingAddress": {
"line1": "123 Main Street",
"line2": "Suite 100",
"city": "Austin",
"stateOrProvince": "TX",
"postalCode": "78701",
"country": "US"
}
},
"order": {
"externalOrderNumber": "YOUR-ORDER-12345",
"orderType": "New",
"lineItems": [
{
"sku": "1-P-SP-TX-XX-A-E-L-0325",
"quantity": 1,
"pricePerUnit": 0
}
],
"shippingAmount": 0,
"taxAmount": 0,
"payment": {
"transactionId": "YOUR-ORDER-12345",
"paymentDate": "2026-02-15T10:00:00Z",
"amount": 0
}
}
}
Note: All pricing fields are $0 because PCC applies your contracted rates automatically.
Use "orderType": "Renewal" with originalOrderNumber when re-ordering for an existing customer - it extends their existing subscription instead of creating a duplicate (see Step 4).
Add "testMode": true to test without creating real orders. For custom poster text, optionally add "customInstructions": "Your text" to each product line (50 characters maximum); otherwise your token's saved default is used. See Step 4 for details.
PCC will provide you with an API token. Include it in all requests:
Authorization: Bearer YOUR_API_TOKEN
Your token determines:
403 mentioning a missing permission, contact PCC and we will update your token.
Retrieve our product catalog. Cache this data and refresh daily or weekly.
If PCC has already provided you with the list of SKUs you'll be ordering, you can skip this step and go straight to checkout.
Request:
GET /v1/products HTTP/1.1
Host: api.postercompliance.com
Authorization: Bearer YOUR_API_TOKEN
Response:
{
"success": true,
"message": "Successfully retrieved 150 available products (price source: Default)",
"data": {
"products": [
{
"name": "California State & Federal Labor Law Poster - 1 Year Plan - AIO - English",
"productNumber": "1-P-SP-CA-XX-A-E-L-0325",
"price": 149.99,
"longDescription": "Comprehensive California state and federal labor law poster with all required postings, updated automatically for 1 year.",
"photoUrl": "https://media.postercompliance.com/productphotos/1-P-SP-CA-XX-A-E-L-0325.jpg",
"details": {
"posterType": "SP",
"posterTypeDescription": "State Poster",
"state": "CA",
"stateName": "California",
"cityCounty": "XX",
"cityCountyDescription": "California Only",
"language": "English",
"languageDescription": "English",
"isStateFederal": true,
"isCityCounty": false,
"planTerm": 1,
"productType": "Plan",
"productTypeDescription": "1 Yr Plan",
"posterFormat": "AllInOne",
"posterFormatDescription": "All In One"
}
}
],
"totalCount": 150,
"priceLevelApplied": null,
"pricesOverridden": 0
}
}
| Field | Description |
|---|---|
productNumber |
Use this when placing orders. Unique product SKU identifier. |
price |
Retail price. Dropship partners: your contracted rate may differ. |
details.productType |
"Plan" (annual subscription with updates) or "Set" (one-time purchase) |
details.posterFormat |
"AllInOne" (single poster) or "SeparatePosters" (multiple posters) |
details.state |
2-letter state code (e.g., "CA", "TX", "NY") |
photoUrl |
Public HTTPS URL of the product image, or null when the product has no photo. Usable directly as an <img src> or fetchable from your own backend. |
longDescription |
Extended product description (can be null) |
?productType=Plan for subscriptions only, or ?productType=Set for one-time purchases.
productNumber value as the sku field when placing checkout orders.
Standard integrations that charge for shipping can look up shipping-method prices at your account's price level. Dropship partners who ship at $0 don't need this.
Response:
{
"success": true,
"message": "Successfully retrieved shipping prices (source: Account)",
"data": {
"shippingMethods": [
{
"method": "Standard",
"name": "UPS Standard Shipping",
"productNumber": "N-S-SH-UP-XX-X-X-X-0000",
"price": 9.95,
"isDefault": true
}
],
"priceLevelSource": "Account",
"accountNumber": "1234567"
}
}
Standard (default), 2Day, Overnight. price is null when none is set. Unknown accountNumber = default prices, not an error; someone else's account = 403.order.shippingAmount (PCC adds the line), or add one shipping productNumber (N-S-SH-...) as a line item with "shippingAmount": 0. Both together = 400. Dropship: "shippingAmount": 0.accountNumber is optional and must be one of your own accounts. With it, prices come from that account's price level (priceLevelSource: "Account"); without it, your token's default price level or the system default is used.
unitPrice below 0.01, so it cannot price a $0 dropship order. When you submit the checkout with $0 for tax, we calculate it from the shipping address.
For standard integrations where you collect payment, get the tax estimate first:
street / state / zipCode, while checkout uses line1 / stateOrProvince / postalCode. Reusing one address object for both requests will fail validation.
POST /v1/orders/estimate HTTP/1.1
Host: api.postercompliance.com
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
{
"shippingDestinations": [
{
"destinationId": "1",
"shippingAddress": {
"street": "123 Main Street",
"city": "Austin",
"state": "TX",
"zipCode": "78701"
},
"items": [
{
"productNumber": "1-P-SP-TX-XX-A-E-L-0325",
"quantity": 1,
"unitPrice": 149.99
}
],
"shippingAmount": 14.99
}
]
}
Response:
{
"success": true,
"message": "Successfully calculated estimate for 1 destinations",
"data": {
"orderId": null,
"destinationResults": [
{
"destinationId": "1",
"shippingAddress": {
"street": "123 Main Street",
"city": "Austin",
"state": "TX",
"zipCode": "78701",
"country": "US"
},
"subtotal": 149.99,
"shippingAmount": 14.99,
"taxAmount": 13.61,
"total": 178.59,
"lineItemTaxes": [
{
"lineId": null,
"productNumber": "1-P-SP-TX-XX-A-E-L-0325",
"quantity": 1,
"unitPrice": 149.99,
"extendedPrice": 149.99,
"taxAmount": 12.37,
"totalWithTax": 162.36
}
],
"taxCloudCartId": "abc123-def456",
"isExempt": false,
"taxCalculationSuccess": true,
"taxCalculationError": null
}
],
"totalTax": 13.61,
"totalSubtotal": 149.99,
"totalShipping": 14.99,
"grandTotal": 178.59,
"allTaxCalculated": true,
"taxCalculationWarning": null
}
}
taxCalculationSuccess. If false, the tax couldn't be calculated - don't proceed to checkout. Note that taxAmount covers products and shipping, while lineItemTaxes lists the product lines only - so those line taxes can sum to less than taxAmount. taxCloudCartId is informational only - you do not send it back.
Submit the order for fulfillment. Choose the tab that matches your scenario.
/v1/checkout/validate. Nothing is created. Response: data.isValid plus data.errors[] (field + message). Format/length problems come back as HTTP 400 with validationErrors.
{
"account": {
"name": "Acme Corporation",
"shippingAttentionTo": "John Smith",
"locationIdentifier": "CUST-00412",
"email": "customer@email.com",
"phone": "555-123-4567",
"shippingAddress": {
"line1": "123 Main Street",
"city": "Austin",
"stateOrProvince": "TX",
"postalCode": "78701",
"country": "US"
}
},
"order": {
"externalOrderNumber": "YOUR-ORDER-ID",
"orderType": "New",
"lineItems": [
{
"sku": "1-P-SP-TX-XX-A-E-L-0325",
"quantity": 1,
"pricePerUnit": 0,
"customInstructions": "COBRAND: CompanyZ"
}
],
"shippingAmount": 0,
"taxAmount": 0,
"payment": {
"transactionId": "YOUR-ORDER-ID",
"paymentDate": "2026-02-15T10:00:00Z",
"amount": 0
}
}
}
Dropship: send $0 for every price, tax and payment amount. PCC applies your contracted rates.
Standard: send real prices. lineItems + shippingAmount + taxAmount must equal payment.amount.
customInstructions: optional, max 50 characters. Omit to use your token's default.
| Situation | Send | What PCC does |
|---|---|---|
| First order for this location (example above) | account only. No accountNumber. | Creates a ship-to account. Save its accountNumber from the job result. |
| Repeat order, same address | accountNumber only. No account. | Ships to the saved address. |
| Repeat order, new address | accountNumber + account with the full new address. | Replaces the saved address (old line2 is cleared), then ships there. name/email are required by validation but not changed. |
Do not resend an old address with accountNumber on every reorder: it overwrites the saved one each time.
Without accountNumber, PCC matches your existing accounts on line1 + line2 + city + state + postal code. A match is reused unchanged.
{
"order": {
"externalOrderNumber": "YOUR-RENEWAL-ORDER-456",
"orderType": "Renewal",
"originalOrderNumber": "ORD-1234567-A1B2",
"lineItems": [
{
"sku": "1-P-SP-TX-XX-A-E-L-0326",
"quantity": 1,
"pricePerUnit": 0
}
],
"shippingAmount": 0,
"taxAmount": 0,
"payment": {
"transactionId": "YOUR-RENEWAL-ORDER-456",
"paymentDate": "2026-02-15T10:00:00Z",
"amount": 0
}
}
}
Renewal = re-order for a customer who already has a subscription. It extends their term. A New order would create a duplicate subscription.
PCC never bills or emails your customers, so renewals only happen when you submit them.
account is optional: the ship-to account comes from originalOrderNumber. Include account (name, email, full shippingAddress required) only to change name, email, phone, attention, locationIdentifier or address; old line2 is cleared.
Job ends Failed if the original order is not yours or its account is deactivated. Read checkoutResults.message.
Save each result's orderNumber for future renewals.
{
"account": {
"name": "Acme Corporation",
"shippingAttentionTo": "John Smith",
"locationIdentifier": "CUST-00412",
"email": "test@example.com",
"shippingAddress": {
"line1": "123 Test Street",
"city": "Austin",
"stateOrProvince": "TX",
"postalCode": "78701",
"country": "US"
}
},
"order": {
"externalOrderNumber": "TEST-ORDER-001",
"orderType": "New",
"testMode": true,
"lineItems": [
{
"sku": "1-P-SP-TX-XX-A-E-L-0325",
"quantity": 1,
"pricePerUnit": 0
}
],
"shippingAmount": 0,
"taxAmount": 0,
"payment": {
"transactionId": "TEST-ORDER-001",
"paymentDate": "2026-02-15T10:00:00Z",
"amount": 0
}
}
}
There is no separate sandbox environment - use order.testMode: true against the production URL; test submissions never create real accounts, orders, or charges.
Test mode checks fields and SKUs and creates nothing. Not exercised: tax (not calculated) and originalOrderNumber (not looked up - a bogus one passes in test, fails in production).
Perfect for development and integration testing. Response includes "testMode": true and simulated IDs (e.g., TEST-...).
Important: use throwaway externalOrderNumber values for tests (e.g. TEST- prefix). Test and live are deduplicated separately; never reuse a test number for the real order.
Response (HTTP 202 Accepted): a successful checkout returns status 202, not 200 - the order is queued for asynchronous processing. Treat any 2xx as success.
{
"success": true,
"message": "Checkout queued successfully",
"data": {
"jobId": "job_69a1b2c3_a1b2c3d4",
"status": "Pending",
"externalOrderNumber": "YOUR-ORDER-ID",
"testMode": false,
"message": "Checkout order queued for processing",
"statusUrl": "/v1/jobs/job_69a1b2c3_a1b2c3d4",
"externalReferenceId": null
}
}
data.status may already be Completed and message says "idempotent replay".
jobId to check status.
The jobId is an opaque string (e.g. job_69a1b2c3_a1b2c3d4) - store it as text, do not assume a UUID format.
Polling GET /v1/jobs/{jobId} is optional: any 2xx means the order is accepted and PCC is alerted to failures automatically. Poll only if you want failures in your own system (Step 5).
externalOrderNumber must be unique per order. Resending one we already accepted returns the original job, never a second order - even if both requests arrive at once.jobId.Failed, PermanentlyFailed or Cancelled are ignored, so resubmitting after a failure is safe. If the failed attempt had already created the order, the resubmit returns ORDER_ALREADY_EXISTS_INCOMPLETE with that order number - stop and contact support.Idempotency-Key header. One key = one job forever, even if it failed. To retry after a failure, use a new key.
| Field | Required | Description |
|---|---|---|
accountNumber |
No | Top level, next to account and order. Max 50. The PCC account number from an earlier job result. Alone = ship to saved address. With account = replace the saved address first. Must be in your account tree (else 403). Omit on renewals. |
account.name |
New orders | Company name of the ship-to location, max 200. Never a person - that goes in account.shippingAttentionTo. Renewals: omit account entirely, or include name + email + full shippingAddress. |
account.email |
New orders | Contact email, max 100. Same renewal rule as account.name. |
account.shippingAttentionTo |
Recommended | Contact person at the location, max 200. Printed as the attention line. |
account.locationIdentifier |
Recommended | YOUR customer / store / location id, max 35. Saved on new accounts, with accountNumber, and on renewals. Returned in checkoutResults.account.locationIdentifier and on GET/PUT /v1/accounts. Not used for matching. |
account.shippingAddress |
New orders | Where to ship. Same renewal rule as account.name. |
account.shippingAddress.* |
With address | line1 required (max 250), line2 optional (max 250), city required (max 100), stateOrProvince exactly 2 characters, postalCode required (max 20), country 2 characters, default US. |
account.phone |
No | Max 50. Saved on new accounts and renewals. Ignored with accountNumber on a New order. |
account.billingAddress |
No | Same shape as shippingAddress. Defaults to the shipping address on new accounts. Replaces the saved billing address with accountNumber or on renewals. |
account.partnerId |
Usually no | Your partner id from PCC. Usually preset on your token - omit it. If your token has none, PCC returns 400 asking for it. |
order.externalOrderNumber |
Yes | Your order number, max 100. One per order, never reuse. Resending it returns the original job (see Duplicate protection). |
order.orderType |
No | New (default) or Renewal. Use Renewal for a customer who already has a subscription. |
order.originalOrderNumber |
If renewal | PCC order number from original order (e.g., "ORD-1234567-A1B2") |
order.testMode |
No | Set to true for development testing (default: false) |
order.lineItems |
Yes | 1 to 500 lines. Each: sku (a productNumber from Step 2), quantity 1 or more, pricePerUnit 0 or more ($0 needs a dropship token). Unknown SKU = 400 ProductValidationError. At most one shipping SKU (Step 2). |
order.shippingAmount |
Yes | Always send it. Dropship: 0. Standard: the Step 2 price; PCC adds the shipping line. Must be 0 when a shipping SKU is in lineItems. Omitted = treated as 0. |
order.taxAmount |
Yes | Always send it. Dropship: 0; PCC recalculates. Standard: the Step 3 estimate; stored as sent. Omitted = treated as 0. |
externalReferenceId |
No | Optional, max 100. Echoed in the 202 response only. Not stored, not used for duplicate protection. |
order.lineItems[].customInstructions |
No | Custom poster text for this line, max 50 characters. Overrides your token's saved default; omit/blank keeps the default. Trimmed, never truncated. Ignored on shipping lines. |
order.payment |
Yes | transactionId (max 100, what PCC support searches by), paymentDate (ISO 8601), amount. Optional: paymentMethod, cardLast4, cardBrand, authorizationCode. Dropship: amount 0. Standard: must equal lines + shipping + tax within $0.01. |
customInstructions separately on each entry in lineItems. For the same SKU with different text, send separate lines. Existing requests without this field keep their current behavior.
Both /v1/checkout and /v1/checkout/validate return HTTP 400 for text over 50 characters, identifying the line in validationErrors. Shorten it before retrying; no order is queued. The limit follows CRM's text count (JavaScript text.length); some characters, such as emoji, count as two.
Poll the status endpoint to see when your order completes.
GET /v1/jobs/job_69a1b2c3_a1b2c3d4 HTTP/1.1
Host: api.postercompliance.com
Authorization: Bearer YOUR_API_TOKEN
Response (Completed):
{
"success": true,
"message": null,
"data": {
"jobId": "job_69a1b2c3_a1b2c3d4",
"jobType": "CheckoutOrder",
"status": "Completed",
"totalItems": 1,
"processedItems": 1,
"succeededItems": 1,
"failedItems": 0,
"progressPercent": 100,
"createdAt": "2026-02-15T10:00:00Z",
"startedAt": "2026-02-15T10:00:01Z",
"completedAt": "2026-02-15T10:00:15Z",
"errorMessage": null,
"checkoutResults": {
"success": true,
"message": "Checkout completed successfully. Account: 1234567, Order: ORD-1234567-A1B2",
"accountCreated": true,
"account": {
"accountId": "abc12345-1234-5678-9abc-def012345678",
"accountNumber": "1234567",
"name": "Acme Corporation",
"isNew": true,
"resolution": "CreatedNew",
"locationIdentifier": "CUST-00412"
},
"order": {
"success": true,
"message": "Order ORD-1234567-A1B2 created successfully with invoice and payment",
"testMode": false,
"salesOrder": {
"salesOrderId": "def45678-5678-9abc-def0-123456789abc",
"orderNumber": "ORD-1234567-A1B2",
"subtotal": 164.98,
"taxAmount": 13.61,
"totalAmount": 178.59,
"lineItemCount": 2,
"lineItems": [
{
"lineItemId": "line-item-guid-1",
"sku": "1-P-SP-TX-XX-A-E-L-0325",
"productName": "Texas Labor Law Poster - 1 Year Plan",
"quantity": 1,
"pricePerUnit": 149.99,
"extendedAmount": 149.99,
"customInstructions": "COBRAND: CompanyZ"
},
{
"lineItemId": "line-item-guid-2",
"sku": "N-S-SH-UP-XX-X-X-X-0000",
"productName": "UPS Standard Shipping",
"quantity": 1,
"pricePerUnit": 14.99,
"extendedAmount": 14.99,
"customInstructions": null
}
]
},
"invoice": {
"invoiceId": "inv-guid-12345",
"invoiceNumber": "INV-2026-00001234",
"totalAmount": 178.59,
"taxAmount": 13.61,
"status": "Billed"
},
"payment": null,
"processingSteps": [],
"errors": []
},
"processingSteps": [],
"errors": []
}
}
}
PCC added the shipping line and applied contracted rates - totals will not match the submitted $0 line values.
invoice.status "Billed", payment null. Consolidated billing = invoice null. Standard = "Paid" + payment object.CreatedNew, FoundByShippingAddress, ProvidedAccountNumber, FromOriginalOrder, TestMode; suffix (Updated) when account was sent with accountNumber or on a renewal; CorrectedFromOrder when PCC staff changed the ship-to account.checkoutResults.errors[].errorCode. ORDER_ALREADY_EXISTS_INCOMPLETE = the order exists from an earlier attempt; stop resubmitting and contact PCC with order.salesOrder.orderNumber.
checkoutResults.account.accountNumber - Reuse for future orders from same customer. checkoutResults.account.locationIdentifier is the Location Identifier saved on that account - the id you sent for a new account; a matched account keeps whatever it already had (possibly null).checkoutResults.order.salesOrder.orderNumber - Use to get tracking informationdata.checkoutResults.order.salesOrder.lineItems[].customInstructions
returns the text CRM saved on each line, including a saved token default when one applied.
For example: "customInstructions": "COBRAND: CompanyZ".
Match lines using lineItemId; the same SKU can appear more than once with different text.
The value is null when no instructions were saved, including shipping lines.
Older job results and test-mode results may also return null because no saved value was captured.
Line items and their instructions are a snapshot from processing, so later CRM line edits are not reflected.
This optional response field does not change existing fields or request requirements.
checkoutResults.account and current subtotal, tax and total in
checkoutResults.order.salesOrder.
That sales order also includes shippingAddress (name, contactName, line1, line2, line3, city, stateOrProvince, postalCode, country),
stateCode, statusCode and status.
checkoutResults.currentDetailsAsOf records when these details were read; edits appear within 15 seconds.
If PCC corrected the shipping account, its resolution is CorrectedFromOrder and isNew is false.
Job completion, accountCreated, processing steps, line items, invoice and payment results remain the original processing record.
A changed order total does not itself indicate a new charge or refund.
Test-mode results are unchanged.
If current details are temporarily unavailable, the API returns HTTP 503 with OrderDetailsUnavailable
and Retry-After: 5; retry the same GET after five seconds and do not resubmit the order.
| Status | Meaning | Action |
|---|---|---|
Pending |
Queued for processing | Wait 5-10 seconds, check again |
Processing |
Currently being processed | Wait 5-10 seconds, check again |
Completed |
Order created successfully | Save the orderNumber |
RetryPending |
A transient backend issue occurred; the order will be retried automatically | Keep polling - no action needed. The first retry runs after 1 minute, so nothing changes for at least that long. |
Failed |
Error occurred (validation, bad data, or a backend issue that was not safe to retry automatically). Resubmitting the same externalOrderNumber is always safe - duplicate protection ignores failed attempts. |
PCC customer service is alerted automatically; resubmission is only needed if you want to correct the data yourself. The reason is in checkoutResults.message and checkoutResults.errors. |
PermanentlyFailed |
Failed after 5 retry attempts | PCC customer service is alerted automatically; contact support or resubmit only if you want to handle it yourself |
Cancelled |
The job was cancelled before completing (rare; typically by PCC support) | Safe to resubmit - duplicate protection ignores cancelled jobs |
RetryPending and is re-attempted up to 5 times with increasing delays
(1, 5, 15, 30, 60 min), ending in PermanentlyFailed only if all retries
are exhausted. Just keep polling; no resubmission needed.
A Failed status means a non-retryable problem (validation, bad
data) - PCC customer service is alerted automatically, so resubmission is only needed if you want to
correct the data yourself: check checkoutResults.errors, fix it, and resubmit.
Resubmitting is safe: reusing the same order.externalOrderNumber returns the original
order if one was created and never creates a duplicate (see "Duplicate protection" above).
Most orders complete within 30-60 seconds.
Once an order ships, retrieve the tracking number to share with your customer.
Tracking is poll-only today - there is no webhook. PCC can also email fulfillment notifications (your order number, our order number, and the tracking number) to addresses you provide - ask your PCC contact to enable fulfillment emails for your integration.
GET /v1/orders/ORD-1234567-A1B2/tracking HTTP/1.1
Host: api.postercompliance.com
Authorization: Bearer YOUR_API_TOKEN
Response (Shipped):
{
"success": true,
"message": "Tracking information retrieved for order ORD-1234567-A1B2",
"data": {
"orderNumber": "ORD-1234567-A1B2",
"trackingNumber": "1Z999AA10123456784",
"found": true,
"isRenewal": false,
"errorMessage": null
}
}
Response (Not Yet Shipped - HTTP 404):
{
"error": "NotFound",
"message": "Order not found or not fulfilled (statecode != 3)",
"traceId": "0HN4ABC123"
}
Response (Renewal Order):
{
"success": true,
"message": "Renewal order ORD-1234567-C3D4 - no shipping required",
"data": {
"orderNumber": "ORD-1234567-C3D4",
"trackingNumber": null,
"found": true,
"isRenewal": true,
"errorMessage": "Renewal orders do not require shipping and will not have tracking numbers"
}
}
trackingNumber: null and isRenewal: false = fulfilled, carrier number not in yet - keep polling. 200 with a trackingNumber = done. Never match on errorMessage text.isRenewal: true, so do not poll tracking for them.
Check tracking for multiple orders at once:
Request:
POST /v1/orders/tracking HTTP/1.1
Host: api.postercompliance.com
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
{
"orderNumbers": [
"ORD-1234567-A1B2",
"ORD-1234567-C3D4",
"ORD-7654321-E5F6"
]
}
Response:
{
"success": true,
"message": "Retrieved tracking for 2 of 3 order(s)",
"data": {
"totalRequested": 3,
"totalFound": 2,
"results": [
{
"orderNumber": "ORD-1234567-A1B2",
"trackingNumber": "1Z999AA10123456784",
"found": true,
"isRenewal": false,
"errorMessage": null
},
{
"orderNumber": "ORD-1234567-C3D4",
"trackingNumber": null,
"found": true,
"isRenewal": true,
"errorMessage": "Renewal orders do not require shipping and will not have tracking numbers"
},
{
"orderNumber": "ORD-7654321-E5F6",
"trackingNumber": null,
"found": false,
"isRenewal": false,
"errorMessage": "Order not found or not fulfilled (statecode != 3)"
}
]
}
}
When a customer moves, corrects a typo in their address, or changes the contact who receives the shipment, update their PCC account directly so the next order ships to the right place. You can read and update any account that was created under your partner account - and no others.
Use the accountNumber from the checkout job result (checkoutResults.account.accountNumber, see Step 5). GET /v1/accounts/{accountNumber} returns the current values.
Request (send only what changes; an address is replaced as a whole):
PUT /v1/accounts/1234567 HTTP/1.1
Host: api.postercompliance.com
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
{
"shippingAddress": {
"line1": "456 Warehouse Blvd",
"line2": "Dock 3",
"city": "Cambridge",
"stateOrProvince": "MA",
"postalCode": "02139",
"country": "US"
}
}
Fields: name, phone, shippingEmail, shippingAttentionTo, locationIdentifier (max 35), billingEmail, billingAddress, shippingAddress.
line2 and line3 are cleared unless you send a new line2.line1, city, stateOrProvince, postalCode; state and country exactly 2 characters.Response (this is the only place shippingAttentionTo is returned - GET /v1/accounts/{accountNumber} does not include it):
{
"success": true,
"message": "Account updated successfully",
"data": {
"accountNumber": "1234567",
"accountId": "abc12345-1234-5678-9abc-def012345678",
"name": "Acme Corporation",
"phone": "555-123-4567",
"shippingEmail": "shipping@customer.com",
"shippingAttentionTo": "Jane Doe",
"locationIdentifier": "CUST-00412",
"billingEmail": null,
"billingAddress": null,
"shippingAddress": {
"line1": "456 Warehouse Blvd",
"line2": "Dock 3",
"city": "Cambridge",
"stateOrProvince": "MA",
"postalCode": "02139",
"country": "US"
}
}
}
| Code | Meaning |
|---|---|
404 | "Account with number '1234567' not found or not accessible" - that account is not under your partner account (or does not exist). Check the accountNumber; the API never confirms accounts that are not yours. |
403 | Your token lacks the account self-service permission. Contact PCC to have it enabled. |
400 | Validation error - an address is missing a required field, a state or country is not 2 characters, or an email is malformed. The offending fields are listed in validationErrors with the first error repeated in message (details is null for these, see Error Handling). |
Add updateOpenOrders: true and send a new UUID in the Idempotency-Key header. This option accepts only name and shippingAttentionTo. Send address, email, phone or other account changes in a separate account-only request.
PUT /v1/accounts/1234567 HTTP/1.1
Host: api.postercompliance.com
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
Idempotency-Key: 82135f58-1cef-4bf7-bad4-32aa1f0f21bf
{
"name": "Evergreen Recycling",
"shippingAttentionTo": "Fidencio Palomera",
"updateOpenOrders": true
}
data.status, data.orders and data.shippingReleased. A saved request may still be waiting for verification or the shipping queue refresh.Poll GET /v1/order-maintenance/{requestId} using the request ID returned in data.requestId. This is the UUID sent in your Idempotency-Key, so you can check it even if the original response was lost. The status call only reads the result. Repeat the original PUT with the same Idempotency-Key and identical body to resume verification or release a confirmed shipping hold; do not create another key after a timeout. A different body with the same key is rejected. If the result stays pending, contact PCC with the request ID.
Your token needs the separate OrdersCancel permission. It is not enabled automatically by the ability to create orders. Contact PCC to enable it.
POST /v1/orders/ORD-1234567-ABCD/cancel HTTP/1.1
Host: api.postercompliance.com
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
Idempotency-Key: 1a20aaeb-4a9b-47f3-8206-88b295d4e97b
{
"reason": "Customer no longer needs this order."
}
The order must belong to your account hierarchy and be unfulfilled, with no tracking, purchased label, unresolved shipment, invoice or finalized accounting/tax information. An order being invoiced cannot be canceled through this endpoint. The API uses CRM's cancellation action and verifies the result. An already-canceled order returns its existing cancellation result.
This endpoint does not cancel an invoice, refund a payment, cancel a subscription or send a customer email. Orders that need those actions must be handled by PCC Customer Service.
Use the same request-status endpoint and retry rules described above, repeating the original POST when a retry is needed. Keep the request ID and review the returned order outcome; completion requires data.status: "completed" and data.shippingReleased: true.
When something goes wrong, you'll get a clear error response:
{
"error": "ValidationError",
"message": "The Line1 field is required.",
"details": null,
"traceId": "0HN4ABC123",
"timestamp": "2026-02-15T10:00:00Z",
"validationErrors": {
"account.shippingAddress.line1": [
"The Line1 field is required."
]
}
}
validationErrors is set only for request-format failures. Business failures (missing account, unknown SKU, $0 without dropship) put the detail in details with validationErrors: null. Always read both.ValidationError 400 · ProductValidationError 400 (unknown SKU in details) · AccountNumberRequired 400 · Forbidden 403 · insufficient_scope 403 (body has required[], no details) · NotFound 404 · RateLimitExceeded 429 · OrderDetailsUnavailable 503. Idempotency-Key over 200 characters = 400.
Limits apply per API token. When you exceed a rate limit you receive a 429 with a Retry-After header.
| Limit | Value |
|---|---|
| API requests (general) | 100 per minute |
Job-status polling (/v1/jobs/...) | 300 per minute (separate bucket - polling never starves your submissions) |
| Line items per order | 500 |
| Shipping destinations per estimate | 200 (500 items each) |
Order numbers per bulk tracking request (POST /v1/orders/tracking) | 500 |
allTaxCalculated: false with a taxCalculationWarning, and the affected destination carries taxCalculationSuccess: false with $0 tax. Do not treat those totals as final - retry the estimate.
| Code | Meaning | What to Do |
|---|---|---|
400 |
Bad request / validation error | Check validationErrors (field format) and details (unknown SKU, $0 price without dropship, missing partner id), fix the request, resend. No job was created. |
401 |
Not authenticated | Check your API token is correct |
403 |
Not authorized | Your token lacks access, or the accountNumber is not in your tree ("The requested account or order is not available under this token."). A renewal with someone else's originalOrderNumber is NOT a 403: it returns 202 and the job ends Failed. |
404 |
Not found | The job, order or account does not exist or is not under your token. On the tracking endpoints a 404 also means the order has not been fulfilled yet (see Step 6) - keep polling. An unknown SKU is a 400, not a 404. |
429 |
Too many requests (rate limit) | Honor the Retry-After header (typically 60 seconds), then retry |
500 |
Server error | Retry later. Contact support if it persists. |
502 / 503 / 504 |
A backend system we depend on is temporarily unavailable or slow | Transient - retry with backoff (30–60s). For writes, see the retry note below: the request may already have applied, so retry only with the same Idempotency-Key. |
externalOrderNumber (and the same Idempotency-Key): you get the original job back, never a duplicate. Reads are always safe to retry.
The API is versioned in the URL path (/v1/...). Within a version:
traceId from the error response when contacting support.
A quick checklist before you flip your integration to production:
/v1/checkout/validate (a 403 mentioning a missing permission means your token needs an update - contact PCC, see Step 1)./v1/checkout/validate passes for a representative order.testMode order round-trips: 202 accepted, then the job reaches Completed with TEST- ids.externalOrderNumber values never reuse test values.Retry-After on 429 responses.