E-Commerce Integration Guide

Connect your platform to PCC for seamless labor law poster fulfillment

API Base URL

All requests go to:

https://api.postercompliance.com

DROPSHIP Quick Start for Dropship Partners

As a dropship partner, you send orders and we ship directly to your customers under your contracted rates. Here's your simplified flow:

1. Get Products GET /v1/products
→
2. Submit Order POST /v1/checkout
→
3. Get Tracking GET /v1/orders/.../tracking
How Dropship Works:
  • You send orders - Submit customer details and products via API
  • We handle pricing - Send $0 for prices, we apply your contracted rates
  • We calculate tax - Send $0 for tax, we calculate based on ship-to address
  • We ship to your customer - Direct fulfillment from our warehouse
  • We invoice you - Consolidated billing per your agreement

Complete Dropship Checkout Example

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.

Full Integration Guide

1

Authentication

PCC will provide you with an API token. Include it in all requests:

Authorization: Bearer YOUR_API_TOKEN

Your token determines:

Permissions: your token covers Steps 1-6. Step 7 needs an extra permission - ask PCC. If a request returns a 403 mentioning a missing permission, contact PCC and we will update your token.
2

Get Products

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.

GET /v1/products

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
  }
}

Key Product Fields

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)
Filter by type: Add ?productType=Plan for subscriptions only, or ?productType=Set for one-time purchases.
Important: Use the productNumber value as the sku field when placing checkout orders.

Shipping Prices (Optional)

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.

GET /v1/products/shipping?accountNumber=1234567

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"
  }
}
Methods: Standard (default), 2Day, Overnight. price is null when none is set. Unknown accountNumber = default prices, not an error; someone else's account = 403.
Charging shipping at checkout: either send 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.
Price source: 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.
3

Calculate Tax (Optional for Dropship)

Dropship Partners: Skip this step. This endpoint rejects 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:

POST /v1/orders/estimate
Different address shape than checkout: the estimate endpoint uses 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
  }
}
Important: Check 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.
4

Submit Order

Submit the order for fulfillment. Choose the tab that matches your scenario.

Validate first (optional): POST the same body to /v1/checkout/validate. Nothing is created. Response: data.isValid plus data.errors[] (field + message). Format/length problems come back as HTTP 400 with validationErrors.
POST /v1/checkout
{
  "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.

SituationSendWhat 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 addressaccountNumber only. No account.Ships to the saved address.
Repeat order, new addressaccountNumber + 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
  }
}
Resubmit response: 202 with the existing job; data.status may already be Completed and message says "idempotent replay".
What happens next: Orders are processed asynchronously. Save the 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).
Duplicate protection:
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.
Lost response (timeout)? Resend the same payload. You get the same jobId.
Jobs that ended 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.
Dedup is per API token. After a token rotation, do not resend old order numbers.
Optional: an Idempotency-Key header. One key = one job forever, even if it failed. To retry after a failure, use a new key.

Order Fields Reference

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.
Different text per product: set 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.
5

Check Order Status

Poll the status endpoint to see when your order completes.

GET /v1/jobs/{jobId}
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 / payment: PCC-billed dropship (above) = invoice.status "Billed", payment null. Consolidated billing = invoice null. Standard = "Paid" + payment object.
resolution: 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.
Failed jobs: read checkoutResults.errors[].errorCode. ORDER_ALREADY_EXISTS_INCOMPLETE = the order exists from an earlier attempt; stop resubmitting and contact PCC with order.salesOrder.orderNumber.
Address corrections on accepted orders: An address that fails postal validation keeps the order on hold; it does not discard valid, priced product lines from the draft. PCC can correct the address and continue the same order without duplicating its products. Address, tax and payment checks must pass before the order is submitted or invoiced. Do not submit a replacement checkout for an order awaiting correction.
Key fields to save:
  • 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 information
Verify custom poster instructions: data.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.
Current order details: For real checkout orders, job lookups return the current shipping account in 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.
Manual fulfillment review: PCC can configure your API token to place new orders in Preorder until staff releases them. Your requests and accepted/job responses stay the same. A completed job means the order was created, not shipped. No additional request field is needed, and billing follows your existing agreement.

Status Values

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
Handling failures: A transient failure (e.g. a temporary backend issue before the order is created) is retried for you automatically - the job shows 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.

6

Get Tracking

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/{orderNumber}/tracking
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"
  }
}
Results: 404 = not fulfilled yet (or wrong number / not yours / transient error) - keep polling. 200 with trackingNumber: null and isRenewal: false = fulfilled, carrier number not in yet - keep polling. 200 with a trackingNumber = done. Never match on errorMessage text.
When to check:
  • Orders typically ship within 1-3 business days
  • Start checking 24 hours after order submission
  • Check once every 4-6 hours until tracking is available
Renewal Orders: Renewal orders do not ship and never have tracking numbers - tracking queries return isRenewal: true, so do not poll tracking for them.

Bulk Tracking (Multiple Orders)

Check tracking for multiple orders at once:

POST /v1/orders/tracking

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)"
      }
    ]
  }
}
7

Update a Customer's Account

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.

PUT /v1/accounts/{accountNumber}

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.

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"
    }
  }
}
CodeMeaning
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.
403Your token lacks the account self-service permission. Contact PCC to have it enabled.
400Validation 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).
Account-only updates do not rewrite an existing order's saved recipient or address. The shipping company name comes from its linked account. Use the option below to correct Attention To on eligible existing orders too. Contact PCC support to redirect an existing order's address or correct an already-created label.

Correct the company name and Attention To on open orders

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
}

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.

Cancel an order before fulfillment

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.

!

Error Handling

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."
    ]
  }
}
Reading errors: 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.
error values: 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.

Rate & Request Limits

Limits apply per API token. When you exceed a rate limit you receive a 429 with a Retry-After header.

LimitValue
API requests (general)100 per minute
Job-status polling (/v1/jobs/...)300 per minute (separate bucket - polling never starves your submissions)
Line items per order500
Shipping destinations per estimate200 (500 items each)
Order numbers per bulk tracking request (POST /v1/orders/tracking)500
Polling guidance: after submitting an order you'll receive a job id. Poll its status every 5–10 seconds; most orders complete within 30–60 seconds. Faster polling gains nothing and consumes your polling budget.
Estimate tax honesty: if tax calculation fails for a destination, the estimate response sets 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.

HTTP Status Codes

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.
Retrying writes: a 5xx or timeout may land after the order was created. Resend the same externalOrderNumber (and the same Idempotency-Key): you get the original job back, never a duplicate. Reads are always safe to retry.

API Versioning & Changes

The API is versioned in the URL path (/v1/...). Within a version:

Need help? Include the traceId from the error response when contacting support.
✓

Before You Go Live

A quick checklist before you flip your integration to production: