Developer notes
Everything the add-on sends to or reads from AliExpress, with its source in the official AliExpress Open Platform documentation
(openservice.aliexpress.com). The pages were read on 2026-11-08 through the documentation site’s own JSON handlers and saved in the
repository (research/aliexpress-api/: <method>.json = API reference, doc<id>.json = guide pages, *.txt = readable extracts).
Items that could not be verified in the text documentation are marked UNVERIFIED with the behaviour chosen.
Sources
Section titled “Sources”| Page | Where |
|---|---|
| API endpoint URLs (doc 1369) | https://openservice.aliexpress.com/doc/doc.htm#/?docId=1369 |
| Signature algorithm (doc 1367) | https://openservice.aliexpress.com/doc/doc.htm#/?docId=1367 |
| HTTP request sample (doc 1366) | https://openservice.aliexpress.com/doc/doc.htm#/?docId=1366 |
| Calling parameters (doc 1368), Error messages (doc 1372) | same site, docId 1368 / 1372 |
| Seller authorization introduction (doc 1364), Configure seller authorization (doc 1365) | same site, docId 1364 / 1365 |
| Become a developer (1362), Register an application (1361), Retrieve App Key and App Secret (1360), Request API Permission (1359) | same site |
| API reference, category “System Tool” (cid 3): /auth/token/create, /auth/token/refresh | https://openservice.aliexpress.com/doc/api.htm#/api?cid=3&path=/auth/token/create |
| API reference, category “AE-Dropshipper” (cid 21038): aliexpress.ds.product.get, aliexpress.ds.freight.query, aliexpress.ds.order.create, aliexpress.trade.ds.order.get, aliexpress.ds.order.tracking.get | https://openservice.aliexpress.com/doc/api.htm#/api?cid=21038&path=aliexpress.ds.product.get (and the other paths) |
Protocol (Client\Api)
Section titled “Protocol (Client\Api)”| Rule | Source | Implementation |
|---|---|---|
Business APIs: https://api-sg.aliexpress.com/sync?method={api_path}&{query}; system (authorization) APIs: https://api-sg.aliexpress.com/rest{api_path}?{query} |
doc 1369 | Api::call() POSTs to /sync?method=<api> with every parameter (also method) form-encoded in the body; Api::system() POSTs to /rest/auth/token/.... “All APIs can initiate requests using POST” (doc 1366, notes). |
Common parameters app_key, timestamp (GOP: milliseconds, less than 7200 s from UTC), sign_method, access_token (business APIs), sign |
API reference “Common Parameters”; doc 1366 | timestamp = round(microtime(true) * 1000), sign_method = sha256, access_token only on business calls. |
Signature: sort every parameter except sign (and byte-array parameters) by name in ASCII order; concatenate name + value; for system APIs put the API path in front; HMAC-SHA256 with the App Secret; upper case hex |
doc 1367, doc 1366 | Api::sign(). Parameters with an empty value are skipped in the signature and not sent (doc 1367 Java sample: areNotEmpty(key, value)). Verified with both published vectors (App Secret “helloworld”): business F7F7926B...EFB5BBB, system 35607762...9F4D81 (tests/unit.php). |
Business APIs: method is one of the signed parameters |
doc 1367 step 1 (“api_path should be used as the request parameter to participate in sorting”), doc 1366 step 3 | Included. Note: the doc 1366 sample signs method=aliexpress.logistics.redefining.getonlinelogisticsinfo while its URL names another method; the vector is reproduced exactly as printed. |
| Object parameters are sent as JSON text | API reference (queryDeliveryReq, param_place_order_request4_open_api_d_t_o, ds_extend_request, single_order_query are “Object”) |
Api::flatten(): arrays to JSON (unescaped Unicode and slashes), booleans to true/false. UNVERIFIED: the text docs do not show the wire form of an Object parameter; JSON text is what the published example values show (queryDeliveryReq = {"quantity":1,...}). |
| Errors: categories SYSTEM, ISV (business data) and ISP (backend service; “try again after some time”) | doc 1372 | Api::error(): ISP and isp.* codes are retryable; token / app / signature codes (IllegalAccessToken, IllegalRefreshToken, InvalidAppkey, IncompleteSignature, InvalidCode, AUTH_TYPE_UNSUPPORTED) mark the account “refused” and are never retried. |
| Answer envelope | UNVERIFIED in the text docs (the reference shows only the inner fields) | Api::unwrap() accepts both the gateway wrapper {"<method with _>_response": {...}} / {"error_response": {"code","msg","type","request_id"}} and the flat form; Api::listOf() accepts a list either plain or wrapped in an object with one key ({"ae_item_sku_info_d_t_o": [...]}, {"number": [...]}). |
| Rate limits | UNVERIFIED: the Dropshipper reference names no figure; the app category defines an “API traffic control policy” (doc 1361) visible in the App Console | Setting “AliExpress requests per second” (default 1), spacing per process; HTTP 429 / ApiCallLimit are retried by the queue with back-off. |
| User-Agent | Not required by the docs | KavioLabs-AliExpress-Dropshipping/1.0 (+https://cs-cart.kaviolabs.com). |
Authorization (Client, Accounts)
Section titled “Authorization (Client, Accounts)”| Step | Source | Implementation |
|---|---|---|
Authorization URL https://api-sg.aliexpress.com/oauth/authorize?response_type=code&force_auth=true&redirect_uri=${app call back url}&client_id=${appkey} |
doc 1364 step 1 | Client::authorizeUrl(). The add-on also sends state (one-time, 32 hex, bound to the admin session, prefixed A/V for the panel). UNVERIFIED: doc 1364 shows the parameter table as an image only; whether AliExpress echoes state is not stated. An answer without the session’s state is refused (login CSRF protection); the fallback is the manual code field. |
| The code comes back to the callback URL registered with the app | doc 1364 step 3, doc 1361 (“callback URL (the redirect URL used for seller authorization)”) | Callback = storefront index.php?dispatch=kv_aliexpress_dropshipping.oauth (one stable URL for admin and vendor panels); it only forwards code/state to admin.php or vendor.php ?dispatch=kv_aliexpress_dropshipping.oauth_return, where the signed-in session is checked. |
| Code lifetime | doc 1364: “will expire within 30 minutes”; /auth/token/create error InvalidCode: “The current validity period of the code is three minutes” (conflict) | The add-on exchanges the code immediately; the manual-code help text says “within a few minutes”. |
/auth/token/create parameter code (required), uuid (optional, not sent); answer access_token, refresh_token, expires_in, refresh_expires_in, expire_time, refresh_token_valid_time, user_id, user_nick, account, … |
API reference cid 3 | Client::exchange(), Accounts::storeToken(); token JSON sealed (AES-256-GCM). |
/auth/token/refresh parameter refresh_token; “the duration of the access token will be reset, but the duration of the refresh token will not”; “only when refresh_expires_in > 0 you can call /auth/token/refresh”; “recommended to refresh it 30 minutes before the token expires”; “save the latest refresh_token” |
API reference cid 3, doc 1364 “Refresh authorization steps” | Client::accessToken(): refresh when less than 30 minutes are left, only with refresh_expires_in > 0, under a per-account GET_LOCK, re-reading the row under the lock, storing the new pair. expire_time / refresh_token_valid_time (milliseconds) win over the relative values. |
| App Key / App Secret | doc 1360 | Entered by the store owner, row company_id = 0; vendors authorize through the owner’s app (doc 1365: “Authorized User Limit” per app category). |
AE-Dropshipper calls
Section titled “AE-Dropshipper calls”aliexpress.ds.product.get (Catalog::fetch, Client::check)
Section titled “aliexpress.ds.product.get (Catalog::fetch, Client::check)”| Parameter | Required | Sent |
|---|---|---|
product_id (Number) |
yes | id parsed from the link or entered |
ship_to_country (String) |
yes | rule “Ship-to country” (default US) |
target_currency |
no | USD |
target_language |
no | rule “Product language” (default en) |
remove_personal_benefit, biz_model, province_code, city_code |
no | not sent |
Read fields: result.ae_item_base_info_dto.{product_id, subject, product_status_type, category_id, detail, mobile_detail},
result.ae_item_sku_info_dtos[].{sku_id, id, sku_attr, offer_sale_price, sku_price, sku_available_stock, currency_code, barcode, ae_sku_property_dtos[].{sku_property_name, sku_property_value, property_value_definition_name, sku_image}},
result.ae_multimedia_info_dto.image_urls (“;”-separated, per the example), result.package_info_dto.gross_weight (read as kg), result.ae_store_info.{store_name, store_country_code}, rsp_code (200 = success), rsp_msg.
Error codes handled: ITEM_ID_NOT_FOUND, Item is not allowed to this country (product marked gone), System Error.
- On sale =
product_status_type=onSelling(example value). UNVERIFIED: the other values are not listed; anything else counts as off sale. - Cost =
offer_sale_pricewhen > 0, elsesku_price(USD per the field description “Value range: 0.01-100000; Unit: USD”). An answer in another currency stops the import. - Order line key =
sku_attr(falls back toid, “sku attribute unique key”);sku_ididentifies the variant for freight and tracking. - UNVERIFIED:
gross_weightunit (kg assumed from the example “0.050”).
aliexpress.ds.freight.query (Freight, RateCalculator)
Section titled “aliexpress.ds.freight.query (Freight, RateCalculator)”queryDeliveryReq (Object, required): quantity, shipToCountry, productId, selectedSkuId, language (en_US), locale (en_US), currency (USD) - all required; province optional (sent with the order’s province name), provinceCode/cityCode not sent (doc: send either province or provinceCode, not both).
Read: result.success, result.code, result.msg, result.delivery_options[].{code, company, shipping_fee_cent, shipping_fee_format, shipping_fee_currency, free_shipping, min_delivery_days, max_delivery_days, ship_from_country, tracking}.
code is “delivery service code, which is used for place order” and goes into logistics_service_name.
Errors DELIVERY_NOT_AVAILABLE_TO_YOUR_ADDRESS and DELIVERY_INFO_EMPTY mean “no option”.
- UNVERIFIED (conflict in the reference):
shipping_fee_centis described as “shipping fee amount in cent” but its example is1.99next toshipping_fee_format“US $1.99”.Mapper::fee()reads a value with a decimal point as an amount, and a whole number as cents only when the formatted text confirms it (650 with “US $6.50” = 6.50). - Tracked only: options with
tracking= false are skipped unless the rule is switched off. - The query is one product and SKU at a time; checkout sums the chosen option per SKU (cached 6 hours, 8 SKUs per package at most).
aliexpress.ds.order.create (Fulfilment::push)
Section titled “aliexpress.ds.order.create (Fulfilment::push)”param_place_order_request4_open_api_d_t_o (required):
| Field | Required | Sent |
|---|---|---|
out_order_id |
no (“outer order id, used for idempotent checkout”) | <prefix>-<order id>, written to the order map before the call |
logistics_address.address, city, country, province |
yes | shipping address (billing as fallback); province = state name, the city when the address has no state (UNVERIFIED whether AliExpress accepts that for every country) |
logistics_address.address2, zip, full_name, contact_person, mobile_no, phone_country, locale |
no | from the order; phone split into digits and the country calling code (+1) from an ITU E.164 table; locale = en_US |
logistics_address.cpf / tax_number |
no | customer tax id from the chosen profile field (cpf for BR) |
product_items[].product_id, product_count |
yes | AliExpress product id, quantity |
product_items[].sku_attr, logistics_service_name, order_memo |
no | SKU key, chosen delivery code, rule “Note to the seller” |
ds_extend_request.payment.{pay_currency, try_to_pay}: USD and "true"/"false" (String, as in the example) from the rule “Payment of AliExpress orders”. Not sent: promotion, trade_extra_param (wholesale, customize map, birthday required for Brazil - not supported in 1.0: Brazilian orders may need it, flagged in the order note by AliExpress), channel_strategy.
Answer: result.{is_success, order_list, error_code, error_msg}; order_list (Number[]) can hold several ids (AliExpress splits per seller); each id becomes a row of kv_aliexpress_dropshipping_ae_orders.
Error codes handled: REPEATED_ORDER_ERROR (never answered with a second order: the order needs attention), B_DROPSHIPPER_DELIVERY_ADDRESS_VALIDATE_FAIL, DELIVERY_METHOD_NOT_EXIST, INVENTORY_HOLD_ERROR, PRICE_PAY_CURRENCY_ERROR and the others listed (order failed with the message).
UNVERIFIED: that a second create with the same out_order_id answers REPEATED_ORDER_ERROR. The reference only says out_order_id is “used for idempotent checkout” and lists REPEATED_ORDER_ERROR (“Duplicate order”); the link between the two, its time window and whether a closed order still blocks are not documented. The add-on therefore never relies on it automatically: it sends a second time only after an administrator confirms they checked AliExpress (“Send again”).
Outcome classes after the claim: is_success = true with ids -> placed (S); REPEATED_ORDER_ERROR -> attention (A); is_success = false with an error_code, or an ISV gateway error with a code -> failed (E, nothing created); HTTP 429, ApiCallLimit, token / app errors and SYSTEM gateway errors (doc 1372: request failed basic verification) -> back to P (not processed); everything else (no answer, HTTP 5xx / 408 / 425, ISP errors, System Error, an answer that is not JSON or has neither ids nor an error code) -> outcome unknown (U), never sent again automatically. Automatic runs place an order only while its status is the push status (or Complete), re-checked right before the call.
UNVERIFIED: sku_attr of product.get (example 73:175#Black Green;71:193#Polarized, with the custom value names) is sent as is; the order.create example shows 14:70221 without names. A refusal is shown on the order.
Idempotency (house checklist): order map row with out_order_id written before the call; GET_LOCK per order; atomic claim state P|E -> U (UPDATE ... WHERE state = old, ROW_COUNT() = 1); no answer, HTTP 5xx, 408/425 or an ISP error leave U and the job is not retried, because the Dropshipper API offers no lookup by out_order_id (the only order query, aliexpress.trade.ds.order.get, needs the AliExpress order id). The administrator links the id (verified with aliexpress.trade.ds.order.get under the same lock, never an id of another order) or sends again with the same out_order_id. Rate limits and authorization errors before processing return the row to P (the cron queues it again after 10 minutes).
aliexpress.trade.ds.order.get (Fulfilment::poll, Fulfilment::link)
Section titled “aliexpress.trade.ds.order.get (Fulfilment::poll, Fulfilment::link)”single_order_query.order_id (required). Read: result.order_status, logistics_status, order_amount.{amount, currency_code}, child_order_list[].{product_id, sku_id, product_count, end_reason}, logistics_info_list[].{logistics_no, logistics_service}.
Errors: isv.invalid-authorization (“Accessing data not belonging to you”) refuses a link; isp.service-unavailable is retried.
order_idis sent as a number (the reference types it Number).- UNVERIFIED: the values of
order_statusandlogistics_statusare not listed in the reference (examples:FUND_PROCESSING,SELLER_SEND_GOODS). The add-on does not rank them: it stores them for display, treats an order whose every child order has anend_reasonas closed, and treatsFINISH(without end reasons) as finished for the optional “delivered” status. Tracking numbers, not statuses, trigger shipments.
aliexpress.ds.order.tracking.get (Fulfilment::poll)
Section titled “aliexpress.ds.order.tracking.get (Fulfilment::poll)”ae_order_id and language (en_US), both required. Read: result.ret, result.code, result.msg, result.data.tracking_detail_line_list[].{mail_no, carrier_name, cp_name, cp_website_url, eta_time_stamps, detail_node_list[].{tracking_name, tracking_detail_desc, time_stamp}, package_item_list[].{item_id, sku_id, quantity}}. TRACKING DATA NOT FOUND means “no parcel yet”. UNVERIFIED: package_item_list[].item_id is read as the AliExpress product id (its example value is a product id; the field name says item).
Tracking link: the API returns the carrier’s website (cp_website_url), not a page per parcel. The customer link is built from the rule: Cainiao global tracking https://global.cainiao.com/newDetail.htm?mailNoList=<number> (default), 17TRACK https://t.17track.net/en#nums=<number>, the carrier website, or an own https template. UNVERIFIED: Cainiao and 17TRACK pages are public tracking pages of third parties, not part of the AliExpress API. Numbers are accepted only as [A-Z0-9_.-]{4,40} (the order.get example is CP0_318495001) (safe in the URL and in CS-Cart’s sprintf carrier link). For the store owner’s account a fixed page is set as the carrier of the shipment (Tygh\Shippings\Services\KvAliexpressDropshipping::getInfo()), so CS-Cart itself shows the link in the order page and e-mails; otherwise the link is in the shipment comment.
Not used (and why)
Section titled “Not used (and why)”- Other Dropshipper APIs (
aliexpress.ds.text.search,aliexpress.ds.category.get,aliexpress.ds.feed.*,aliexpress.ds.image.searchV2,aliexpress.ds.order.afterpay, …): outside the 1.0 scope (import by link or id, place, track). - There is no AliExpress cancel call in the Dropshipper category; cancellations are flagged.
- No webhooks: tracking and stock are polled.
| Table | Content | Uninstall |
|---|---|---|
kv_aliexpress_dropshipping_accounts |
App Key, sealed App Secret, sealed token JSON, user nick, auth state | kept (retention) |
kv_aliexpress_dropshipping_order_map |
one row per CS-Cart order: out_order_id, state, AliExpress ids, lines | kept |
kv_aliexpress_dropshipping_ae_orders |
one row per AliExpress order id: status, amount, tracking numbers -> shipments | kept |
kv_aliexpress_dropshipping_product_map, _variant_map |
product and SKU links (sku_id, sku_attr) | kept |
kv_aliexpress_dropshipping_options, _category_map |
rules, category mappings | kept |
kv_aliexpress_dropshipping_jobs, _log, _cache |
queue, log, freight cache | dropped |
Kept tables are dropped only when the root setting delete_data_on_uninstall (“Permanently delete AliExpress order and product links on uninstall”) is on (Retention, generated by kv retention).
Hooks and extension points
Section titled “Hooks and extension points”- PHP hooks:
change_order_status_post(push / cancel jobs),delete_product_post(mapping removal),init_templater_post(Upgrade Synchronizer while pending). - Shipping service module
kv_aliexpress_dropshipping(codescheapest,fastest), classTygh\Shippings\Services\KvAliexpressDropshipping. - Test configuration (config.local.php, development only):
kv_aliexpress_dropshipping_base_url(mock gateway root),kv_aliexpress_dropshipping_callback_url,kv_aliexpress_dropshipping_min_interval_ms,kv_aliexpress_dropshipping_timeout_s(honoured only together with the base URL override).
php81 tests/unit.php/php83 tests/unit.php: signature vectors, envelopes, every mapping with the reference examples (tests/fixtures), token handling, price rules, options, image safety, client requests.node tests/mock-server/server.js 11850: local gateway following the documented contract (signature check, token lifecycle,out_order_ididempotency withREPEATED_ORDER_ERROR, order split per seller, tracking).node tests/e2e/e2e.mjsandtests/e2e/vendor.mjs: refuse to run unless the store’s config points the base URL at the local mock.