PromoStandards is the wholesale promotional products industry’s open data standard. It defines a set of SOAP web services — Product Data, Pricing & Configuration, Media Content, Inventory — that compliant suppliers implement at their own URLs. API-HUB can connect to any of the 994+ suppliers registered in the PS directory without writing supplier-specific code, as long as you have valid credentials.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/VisualGraphxLLC/API-HUB/llms.txt
Use this file to discover all available pages before exploring further.
What PromoStandards is
PromoStandards specifies versioned WSDL contracts. Suppliers implement whichever service versions they support. The platform speaks to the following service types:| Service | PS version | SOAP operation |
|---|---|---|
| Product Data | 2.0.0 | getProduct, getProductSellable, getProductDateModified, getProductCloseOut |
| Pricing & Configuration | 1.0.0 | getConfigurationAndPricing |
| Media Content | 1.1.0 | getMediaContent |
| Inventory | 2.0.0 | getInventoryLevels |
wsVersion, id, and password as authentication fields. Some calls also include localizationCountry and localizationLanguage.
The PS directory
The PS directory is a public registry of all PromoStandards-compliant suppliers. API-HUB fetches it via:Code (the PS supplier code) and metadata. You can look up the WSDL endpoints for a specific supplier:
protocol: promostandards, API-HUB caches the WSDL URLs returned by the directory into the endpoint_cache JSONB column on the supplier record. The adapter reads endpoint_cache at runtime to resolve WSDL URLs for each service type without hitting the directory on every sync.
endpoint_cache_updated_at records when the cache was last refreshed. If a supplier updates their WSDL URLs (rare but possible), you can refresh the cache by fetching GET /api/suppliers/{id}/endpoints, which re-queries the directory and updates the record.Finding a supplier’s PS code
Open the PS directory in the admin UI
Navigate to PS Directory in the left sidebar. The page lists all 994+ registered companies with their codes, names, and supported service versions.
Search by name or code
Use the search box to filter by company name (e.g.
SanMar) or by code (e.g. SANMAR). The Code field is what you paste into the PromoStandards code field when creating a supplier.SOAP communication via zeep
API-HUB uses zeep (a Python SOAP library) wrapped withasyncio.to_thread because zeep is synchronous. Each SOAP call runs in a thread-pool thread and does not block the FastAPI event loop.
WSDL caching
PromoStandardsClient is instantiated once per service type per adapter instance. On first use, zeep parses the WSDL and caches the parsed result to an SQLite file on disk via zeep.cache.SqliteCache. Subsequent instantiations for the same WSDL URL skip re-parsing, which reduces startup time for frequently synced suppliers.
HistoryPlugin is attached to every client so the raw XML envelope is accessible after each call. This is required for SOAP fault classification, which reads the envelope directly rather than relying on zeep’s exception types.
Service bootstrap retry
Some suppliers return a WSDL at a URL that lacks the?wsdl query parameter as a default service binding. If zeep raises ValueError: no default service defined, the client automatically retries with ?wsdl appended to the URL:
SOAP fault classification
PromoStandards suppliers signal errors as SOAP Fault envelopes. The adapter parses every response envelope for a<Fault> element and raises the appropriate Python error type.
| Fault code | Meaning | Error raised | Effect |
|---|---|---|---|
100 | Invalid credentials | AuthError | Job aborts immediately |
104 | Account inactive / not authorized | AuthError | Job aborts immediately |
110 | Password expired | AuthError | Job aborts immediately |
| Any other code | Per-product supplier error | SupplierError | Product skipped, import continues |
TransientError and retry
Network timeouts andzeep.exceptions.TransportError are caught and re-raised as TransientError. The import orchestrator retries up to three times with exponential backoff (4 s → 2 s → 1 s). If all retries are exhausted, the product is logged as skipped and the import continues with the next product.
Product hydration flow
For eachProductRef discovered, PromoStandardsAdapter.hydrate_product() makes up to three SOAP calls:
getProduct— core product data: name, description, brand, categories, parts (color, size)getConfigurationAndPricing— pricing tiers per part, currency USD, price type Net, configuration BlankgetMediaContent— image URLs, media type Image
try/except individually. If either fails, the product is stored with whatever data was successfully retrieved and a warning is logged. A supplier that only exposes Product Data will still produce importable records.
Defensive XML parsing
All XML is parsed with a hardened lxml parser that prevents external entity injection (XXE) attacks:resolve_entities=False— disables entity expansion entirely.no_network=True— prevents the parser from fetching external DTDs or schemas.huge_tree=False— rejects documents that exceed lxml’s default depth and node limits.
etree.fromstring call in the adapter, including fault classification.
SanMarAdapter
SanMar is a PromoStandards supplier with a few platform-specific deviations.SanMarAdapter is a thin subclass of PromoStandardsAdapter that overrides WSDL resolution with hardcoded fallbacks.
Hardcoded WSDLs
SanMar’s PS endpoint cache entries are not always complete.SanMarAdapter._wsdl_for() first checks the PS directory cache, then falls back to these hardcoded URLs:
Extended authentication
SanMar’s non-standard category extension service (getProductInfoByCategory) uses a webServiceUser complex type instead of the standard PS auth fields. PromoStandardsClient._sanmar_auth_payload() builds this type from the same auth_config dict, reading additional keys like customer_number and username alongside the standard id and password.
Category-driven discovery
SanMar publishes a fixed list of 19 categories in their Web Services Integration Guide. The client uses this static list (no SOAP call) to drive category-by-category fetches ofgetProductInfoByCategory. Discontinued products (status Discontinued or title prefixed with DISCONTINUED) are filtered out during normalization.