Knowledge Base Guide

PrestaShop Webservice API: REST Integration, PHP Client & ERP Sync Guide

Complete guide to PrestaShop Webservice API — CRUD operations, PHP client library, authentication, filtering, ERP/POS integration, and PS 9 AdminAPI preview.

What Is the PrestaShop Webservice

The PrestaShop Webservice is a built-in REST-like API that lets external applications read and write store data over HTTP. It exposes resources — products, orders, customers, categories, stock, and dozens more — through a standardized interface. XML is the default format; JSON is available in PS 1.7+ via the &output_format=JSON parameter.

Every PrestaShop since version 1.4 includes the Webservice. It is not a module — it is part of the core. PS 1.6, 1.7, 8.x, and 9.x all support it.

The Webservice is the official way to integrate PrestaShop with external systems. Direct database queries bypass validation, hooks, and business logic. The API does not.

Common use cases: syncing products from an ERP or PIM, exporting orders to fulfillment systems, keeping stock in sync across channels, managing customers from a CRM, and automating bulk operations like price updates.

Enabling and Configuring the API

The Webservice is disabled by default. Enable it at Advanced Parameters > Webservice, then click "Add new webservice key". For each key you set a description, enabled/disabled status, and a permission matrix — a grid of resources and allowed HTTP methods (GET, POST, PUT, DELETE, HEAD).

Create separate API keys for each integration. If your ERP only reads orders and updates stock, do not give it permission to delete products. Principle of least privilege.

The Webservice requires URL rewriting. Apache handles this via .htaccess in /api/. On Nginx, add:

location /api/ {
    try_files $uri $uri/ /webservice/dispatcher.php?$args;
}

Authentication

HTTP Basic Auth — the API key goes in the username field, password is blank:

curl -u "YOUR_API_KEY:" https://your-store.com/api/products

HTTPS is mandatory — keys travel in every request header. IP whitelisting is the single most effective security measure for fixed-IP integrations:

# .htaccess in /api/
<IfModule mod_authz_core.c>
    Require ip 10.0.0.5
    Require ip 192.168.1.0/24
</IfModule>

Rotate keys every 6-12 months. Store them in environment variables, never in source code.

Available Resources

Call the root endpoint to see everything your key can access:

curl -u "KEY:" https://your-store.com/api/

Catalog: products, combinations, categories, manufacturers, suppliers, product_features, product_feature_values, product_options (attribute groups), product_option_values, tags, images

Sales: orders, order_details (line items), order_states, order_histories (status changes), order_carriers (tracking), carts, cart_rules

Customers: customers, addresses, groups

Inventory: stock_availables (main stock resource), warehouses, supply_orders

Configuration: carriers, countries, currencies, languages, taxes, zones, shops

CRUD Operations

GET — Reading Data

# List products (IDs only)
curl -u "KEY:" https://your-store.com/api/products

# Full details
curl -u "KEY:" "https://your-store.com/api/products?display=full"

# Selected fields only (faster)
curl -u "KEY:" "https://your-store.com/api/products?display=[id,name,price,reference]"

# Single product in JSON
curl -u "KEY:" "https://your-store.com/api/products/42?output_format=JSON"

POST — Creating

Get the blank XML schema, fill in required fields, POST it back:

curl -u "KEY:" -X POST -H "Content-Type: application/xml" \
  -d '<prestashop xmlns:xlink="http://www.w3.org/1999/xlink">
  <product>
    <id_category_default>2</id_category_default>
    <id_tax_rules_group>1</id_tax_rules_group>
    <active>1</active><state>1</state>
    <price>29.99</price>
    <name><language id="1">My Product</language></name>
    <link_rewrite><language id="1">my-product</language></link_rewrite>
    <description_short><language id="1">Short desc</language></description_short>
    <associations><categories><category><id>2</id></category></categories></associations>
  </product>
</prestashop>' https://your-store.com/api/products

PUT — Updating

GET the current resource, modify fields, PUT the entire XML back. Partial updates are not supported.

curl -u "KEY:" https://your-store.com/api/products/42 -o product.xml
# Edit product.xml, then:
curl -u "KEY:" -X PUT -H "Content-Type: application/xml" \
     -d @product.xml https://your-store.com/api/products/42
Always GET before PUT. If you construct XML from scratch and forget a required field, the API may clear it or reject the request.

DELETE

curl -u "KEY:" -X DELETE https://your-store.com/api/products/42
# Multiple: DELETE "https://your-store.com/api/products/?id=[42|43|44]"

DELETE is permanent — no trash, no undo. Prefer setting active=0 in production.

Filtering, Sorting, and Pagination

# Exact match
?filter[reference]=ABC-123

# Range (price 10-50)
?filter[price]=[10,50]

# Date range
?filter[date_upd]=[2025-01-01,2025-12-31]

# Starts with (% URL-encoded as %25)
?filter[name]=[Nike]%25

# Combined
?filter[active]=1&filter[id_category_default]=5&display=[id,name,price]

# Sorting
?sort=[price_ASC]    ?sort=[date_upd_DESC]

# Pagination (offset,count)
?limit=50         # first 50
?limit=50,50      # results 51-100

Schema discovery — two modes to inspect resource structure without documentation:

?schema=blank     # Empty XML template with all fields
?schema=synopsis  # Field types, required flags, max lengths, read-only markers

The synopsis tells you exactly which fields are required and what format they expect. Always consult it before building integration code.

Working with Images

Product images are uploaded via multipart form data to a separate endpoint:

# Upload
curl -u "KEY:" -X POST -F "image=@photo.jpg;type=image/jpeg" \
     "https://your-store.com/api/images/products/42"

# List images for product
curl -u "KEY:" "https://your-store.com/api/images/products/42"

# Get specific image / image type
curl -u "KEY:" "https://your-store.com/api/images/products/42/15/large_default"

# Delete
curl -u "KEY:" -X DELETE "https://your-store.com/api/images/products/42/15"

PHP upload:

$ch = curl_init("$shopUrl/api/images/products/$productId");
curl_setopt($ch, CURLOPT_USERPWD, "$apiKey:");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
    'image' => new CURLFile($imagePath, mime_content_type($imagePath))
]);
$response = curl_exec($ch);
Optimize images before uploading. Resize to 2048px max and compress — 5MB DSLR photos will slow your store down.

Combinations and Attributes

Combinations are the trickiest part of the API. The data model has three layers:

  • Product Option (attribute group) — Size, Color, Material
  • Product Option Value — S, M, L, Red, Blue
  • Combination — a set of values for a product (Size:M + Color:Red)

Create in order: attribute group → values → combination:

# Create combination linking to existing attribute values
curl -u "KEY:" -X POST -H "Content-Type: application/xml" \
  -d '<prestashop><combination>
    <id_product>42</id_product>
    <reference>PROD-42-M-RED</reference>
    <price>5.00</price>
    <minimal_quantity>1</minimal_quantity>
    <associations><product_option_values>
      <product_option_value><id>2</id></product_option_value>
      <product_option_value><id>7</id></product_option_value>
    </product_option_values></associations>
  </combination></prestashop>' "https://your-store.com/api/combinations"

The price on a combination is a price impact added to the base product price, not the final price.

Stock per Combination

Stock is managed through stock_availables, not the combination itself:

# Find stock record
curl -u "KEY:" "https://your-store.com/api/stock_availables?filter[id_product]=42&filter[id_product_attribute]=15&display=full"

# Update (GET first, modify quantity, PUT back)
curl -u "KEY:" -X PUT -H "Content-Type: application/xml" \
  -d '<prestashop><stock_available>
    <id>89</id><id_product>42</id_product>
    <id_product_attribute>15</id_product_attribute>
    <quantity>100</quantity>
  </stock_available></prestashop>' "https://your-store.com/api/stock_availables/89"
The product-level stock record (id_product_attribute=0) holds the sum of all combinations. Update combination-level stock and PrestaShop recalculates the total automatically.

Orders and Cart

# Recent orders
curl -u "KEY:" "https://your-store.com/api/orders?display=[id,reference,total_paid,current_state,date_add]&sort=[id_DESC]&limit=20"

# Full order with line items
curl -u "KEY:" "https://your-store.com/api/orders/1234?display=full"
curl -u "KEY:" "https://your-store.com/api/order_details?filter[id_order]=1234&display=full"

Updating Order Status

Create a new order_history record — this triggers the status change and sends the customer email:

curl -u "KEY:" -X POST -H "Content-Type: application/xml" \
  -d '<prestashop><order_history>
    <id_order>1234</id_order>
    <id_order_state>4</id_order_state>
  </order_history></prestashop>' "https://your-store.com/api/order_histories"

Default state IDs: 1=Awaiting payment, 2=Payment accepted, 3=Processing, 4=Shipped, 5=Delivered, 6=Canceled, 7=Refunded. These vary by installation — query order_states for your store's actual list.

Tracking numbers: GET the order_carriers record for the order, add tracking_number, PUT it back.

Creating orders via API is technically possible but not recommended. Order creation involves cart validation, tax calculation, payment processing, stock deduction, and hook execution — the API bypasses most of this. For marketplace imports, create a cart via API and process it through a custom front controller.

PHP Client Library

composer require prestashop/prestashop-webservice-lib

Or download the single file from GitHub.

$ws = new PrestaShopWebservice('https://your-store.com', 'API_KEY', false);

// List products
$xml = $ws->get(['resource' => 'products', 'display' => 'full', 'limit' => 10]);
foreach ($xml->products->product as $p) {
    echo $p->id . ' - ' . $p->name->language . "\n";
}

// Create product from blank schema
$blank = $ws->get(['url' => 'https://your-store.com/api/products?schema=blank']);
$blank->product->active = 1;
$blank->product->price = 29.99;
$blank->product->name->language[0] = 'New Product';
$blank->product->name->language[0]['id'] = 1;
$blank->product->link_rewrite->language[0] = 'new-product';
$blank->product->link_rewrite->language[0]['id'] = 1;
$blank->product->id_category_default = 2;
$result = $ws->add(['resource' => 'products', 'postXml' => $blank->asXML()]);

// Update stock
function updateStock($ws, int $pid, int $qty, int $combo = 0): void {
    $xml = $ws->get([
        'resource' => 'stock_availables',
        'filter[id_product]' => $pid,
        'filter[id_product_attribute]' => $combo,
        'display' => 'full'
    ]);
    $sid = (int)$xml->stock_availables->stock_available->id;
    $sxml = $ws->get(['resource' => 'stock_availables', 'id' => $sid]);
    $sxml->stock_available->quantity = $qty;
    $ws->edit(['resource' => 'stock_availables', 'id' => $sid, 'putXml' => $sxml->asXML()]);
}

// Export orders since date
$xml = $ws->get([
    'resource' => 'orders',
    'display' => '[id,reference,total_paid,current_state,date_add]',
    'filter[date_add]' => '[2025-06-01,9999-12-31]',
    'sort' => '[date_add_DESC]', 'limit' => 500
]);

Integration Patterns

ERP Integration

The most common scenario. Products, prices, and stock flow from ERP to PrestaShop; orders and customer data flow back. Best practices: use reference codes (SKU/EAN) as shared identifiers, schedule stock sync every 15 minutes and products hourly, track the last sync timestamp with filter[date_upd], decide conflict resolution upfront (ERP usually wins), and log everything.

Warehouse and POS

WMS follows a cycle: receive goods → update stock via API; poll orders → ship → update tracking via API. POS integration is similar — stock must sync in near real-time so in-store purchases immediately reduce online availability.

Marketplace Sync

Allegro, Amazon, eBay integrations need product export with images, order import (via cart + custom processing rather than direct order POST), and bidirectional stock sync to prevent overselling.

Accounting

Read-only: pull orders, invoices, and credit slips on a daily/weekly schedule. Needs only GET permissions.

PrestaShop 9 API Changes

The legacy Webservice continues to work in PS 9 — it is not deprecated. Existing integrations function after upgrade.

AdminAPI — New Symfony-Based API

PS 9 introduces a new AdminAPI alongside the legacy Webservice. Key differences:

  • Authentication: OAuth2 with client credentials (vs. API key Basic Auth)
  • Format: JSON only (vs. XML default)
  • Architecture: Symfony / API Platform with OpenAPI/Swagger docs at /admin-api/docs
  • Coverage: Growing — does not yet cover all legacy Webservice resources
# Get OAuth2 token
curl -X POST https://your-store.com/admin-api/access_token \
     -d "grant_type=client_credentials&client_id=ID&client_secret=SECRET"

# Use bearer token
curl -H "Authorization: Bearer eyJ0eXAi..." https://your-store.com/admin-api/products

Tokens expire (default: 1 hour) — handle refresh in your integration. Scopes (product_read, product_write, order_read) control access granularly. Create API clients at Advanced Parameters > AdminAPI.

Use AdminAPI for new PS 9+ integrations. Use the legacy Webservice for broad resource coverage or backward compatibility with PS 1.6/1.7/8.x. You can use both in the same integration.

Common Problems

401 Unauthorized: Wrong key (check whitespace), key disabled, Webservice toggled off, or key sent as query parameter instead of Basic Auth header.

403 Forbidden: Key lacks permission for the resource or HTTP method. Check the permission matrix.

404 Not Found: Resource does not exist or URL rewriting is broken. Test: curl -u "KEY:" "https://store.com/webservice/dispatcher.php?url=products" — if this works but /api/products does not, fix rewrite rules.

XML parsing errors: Use CDATA for special characters (<![CDATA[HTML & more]]>), always wrap in <prestashop> root element, send UTF-8 with proper Content-Type header.

Slow bulk operations: The API processes one request at a time. For 10,000+ items:

  • Request only needed fields with display — avoid display=full on large catalogs
  • Filter by date_upd to sync only changed records
  • Reuse curl handles for persistent connections
  • For initial imports of 50K+ items, consider direct DB with cache clear
// Efficient bulk stock update — reuse handle
$ch = curl_init();
curl_setopt($ch, CURLOPT_USERPWD, "$apiKey:");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/xml']);
foreach ($updates as $stockId => $qty) {
    curl_setopt($ch, CURLOPT_URL, "$url/api/stock_availables/$stockId");
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
    curl_setopt($ch, CURLOPT_POSTFIELDS,
        "<prestashop><stock_available><id>$stockId</id><quantity>$qty</quantity></stock_available></prestashop>");
    curl_exec($ch);
}
curl_close($ch);

Security Best Practices

  • One key per integration — revoke one without breaking others
  • Minimum permissions — stock sync does not need DELETE on products
  • HTTPS always — keys in plain HTTP are visible to network sniffers
  • IP whitelisting — the most effective single measure for server-to-server integrations
  • Key rotation — every 6-12 months
  • Environment variables — never commit keys to source control
  • Rate limiting — the API has none built in; add it at web server level
# Nginx rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
location /api/ {
    limit_req zone=api burst=50 nodelay;
    try_files $uri $uri/ /webservice/dispatcher.php?$args;
}

Audit logging: PrestaShop has no built-in API logging. Parse web server access logs for /api/ requests, or override WebserviceRequest to log timestamp, IP, resource, method, and response code. Essential for GDPR/PCI compliance when customer data is accessed.

Alternatives to the Webservice

Direct Database Access

Orders of magnitude faster for bulk operations, but bypasses validation, hooks, and cache invalidation. Appropriate for one-time imports, read-only reporting, and emergency fixes. Not appropriate for ongoing sync or anything touching orders or payments. Always clear caches after direct DB changes.

Module Hooks for Real-Time Sync

Instead of polling the API, push data via hooks when events happen:

public function hookActionProductUpdate($params) {
    $product = $params['product'];
    $payload = json_encode([
        'id' => $product->id, 'reference' => $product->reference,
        'price' => $product->price,
        'quantity' => StockAvailable::getQuantityAvailableByProduct($product->id),
    ]);
    $ch = curl_init('https://erp.example.com/webhook/product-update');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
    curl_setopt($ch, CURLOPT_TIMEOUT, 5);
    curl_exec($ch); curl_close($ch);
}

Custom Module Endpoints

For operations the Webservice handles poorly, build a custom API as a module front controller. Full control over format, auth, and business logic — process bulk operations in a single request while using PrestaShop's native classes for validation.

Choosing the Right Approach

ScenarioBest Approach
Standard ERP/WMS integrationWebservice API
New PS 9+ integrationAdminAPI (OAuth2, JSON)
Bulk import 50K+ productsDirect DB + cache clear
Real-time event syncModule hooks (webhook push)
Custom business logicCustom module endpoint
Read-only reportingDirect DB queries

The PrestaShop Webservice has been stable for over a decade. For PS 9+, the AdminAPI offers a modern alternative. Choose the right tool for each job, implement it with security and error handling from day one, and always test against the specific PrestaShop version you are targeting.

More guides available

Browse our knowledge base for more practical PrestaShop tutorials, or reach out if you need help.

Loading...
Back to top