Skip to content
Marketplace

Sellers

Create sellers, moderate their products, and read their orders and commissions.

Add Textiles Andinos as a seller in your marketplace, approve their Polo de algodón, and read the order and commission for a sale.

In a marketplace, each seller has its own catalog, stock, and orders. Your store is a seller too, the internal seller (isInternal: true). A seller's product can be linked to one of yours as an offer, and a cart with products from two sellers creates one order for each, with a single payment.

Requirements

  • A marketplace store.
  • An Admin token in ECOMIQ_TOKEN with the settings:create, settings:read, settings:update, catalog:read, catalog:update, and order:read permissions. See Quickstart.
  • For Storefront calls, your store slug in x-Store-Domain.

Create the seller

Send name. If you omit slug, it's generated from name. If you send ownerEmail, the owner gets an invitation; otherwise, you can invite them later. status defaults to active and doesn't wait for the invitation to be accepted.

curl -X POST https://api.ecomiq.pe/api/v1/sellers \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Textiles Andinos",
    "slug": "textiles-andinos",
    "countryCode": "PE",
    "currencyCode": "PEN",
    "legalName": "Textiles Andinos S.A.C.",
    "taxId": "20601234567",
    "commissionPercent": 12,
    "ownerEmail": "rosa.quispe@example.com",
    "ownerFirstName": "Rosa",
    "ownerLastName": "Quispe"
  }'
Response (excerpt)
{
  "id": "7204558912004325310",
  "storeId": "7204558912004325300",
  "name": "Textiles Andinos",
  "slug": "textiles-andinos",
  "countryCode": "PE",
  "currencyCode": "PEN",
  "commissionPercent": 12,
  "isInternal": false,
  "catalogSettings": {
    "publicationEnabled": true,
    "productApprovalPolicy": "manualReview",
    "promotionsEnabled": false
  },
  "legalInfo": {
    "isVerified": false,
    "taxId": "20601234567",
    "legalName": "Textiles Andinos S.A.C."
  },
  "status": "active",
  "createdOn": "2026-10-05T14:00:00Z"
}
FieldInitialWhat it controls
statusactiveactive, inactive, or deleted.
commissionPercent0Default commission, from 0 to 100.
catalogSettings.productApprovalPolicymanualReviewmanualReview keeps the seller's new products in draft.
catalogSettings.promotionsEnabledfalseWhether the seller's promotions apply.
catalogSettings.publicationEnabledtrueWhether its products go to the store's connected channels.
legalInfo.isVerifiedfalseVerified seller. Your store sets it.

The slug is unique within the store.

Import and export sellers

POST /v1/sellers/imports accepts Excel (.xlsx), CSV, TSV, or JSONL and returns 202 with an operation. It requires settings:create and settings:update. Track progress and download row errors with GET /v1/operations/{id}. With ?validateOnly=true, it only validates. These imports don't send invitations.

Each row declares action: create requires name and update requires id. An existing slug never turns a creation into an update. A new seller can inherit the store's country and currency.

action,id,name,slug,status,commission_percent
create,,Textiles Andinos,textiles-andinos,active,12
update,7204558912004325310,Textiles Andinos,,inactive,10

It also accepts country_code, currency_code, description, phone, email_notification, legal_name, tax_id, legal_address, contact_person, and contact_email. An omitted column keeps its value; an empty cell clears an optional text field. Owner details don't go in the file.

POST /v1/sellers/exports accepts format (xlsx or csv), ids, search, and statuses, and returns an operation with the file. Without filters, it exports everything; with template: true, it generates a template.

To edit in a spreadsheet, POST /v1/sellers/spreadsheet takes ids or filters: { search, statuses }, pageSize (up to 200), and maxRows (up to 1000), and returns id, totalCount, and expiresAt. Read the rows with GET /v1/sellers/spreadsheet/{id}?cursor=... for up to one hour and save changes with POST /v1/sellers/imports. The sheet excludes the internal seller.

Limit their catalog

PATCH /v1/sellers/{id} sets review and policies:

  • name is required.
  • Each block you send, such as catalogSettings, replaces the whole block; blocks you omit don't change.
  • deliveryPolicy and privacyAndSecurityPolicy become null if you omit them.
curl -X PATCH https://api.ecomiq.pe/api/v1/sellers/7204558912004325310 \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Textiles Andinos",
    "deliveryPolicy": "Despachamos en 48 horas hábiles a Lima Metropolitana.",
    "privacyAndSecurityPolicy": "Usamos tus datos solo para entregar tu pedido.",
    "catalogSettings": {
      "publicationEnabled": true,
      "productApprovalPolicy": "manualReview",
      "promotionsEnabled": true
    }
  }'

Then limit their categories or brands with PUT /v1/sellers/{sellerId}/scopes. This example restricts them to the Polos category and its subcategories:

curl -X PUT https://api.ecomiq.pe/api/v1/sellers/7204558912004325310/scopes \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "scopes": [
      {
        "type": "category",
        "categoryId": "7204558912004325330",
        "brandId": null,
        "includeChildrenCategories": true
      }
    ]
  }'
Scopes response
[
  {
    "id": "7204558912004325335",
    "type": "category",
    "categoryId": "7204558912004325330",
    "brandId": null,
    "includeChildrenCategories": true,
    "createdOn": "2026-10-05T14:05:00Z",
    "updatedOn": null
  }
]

Without scopes, the seller can use any category or brand. With scopes, every product they create or edit must fall within one. PUT replaces the whole list; "scopes": [] removes them. To offboard the seller, use Delete a seller.

Set the commissions

PUT /v1/sellers/{sellerId}/commissions replaces all of the seller's rules. There's one rule per category and one per brand.

curl -X PUT https://api.ecomiq.pe/api/v1/sellers/7204558912004325310/commissions \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "commissions": [
      {
        "target": "category",
        "categoryId": "7204558912004325330",
        "brandId": null,
        "includeChildrenCategories": true,
        "productCommissionPercent": 10,
        "freightCommissionPercent": 5,
        "fixedFee": 1.5,
        "isActive": true
      }
    ]
  }'
Response
[
  {
    "id": "7204558912004325350",
    "target": "category",
    "categoryId": "7204558912004325330",
    "brandId": null,
    "includeChildrenCategories": true,
    "productCommissionPercent": 10,
    "freightCommissionPercent": 5,
    "fixedFee": 1.5,
    "isActive": true,
    "createdOn": "2026-10-05T14:10:00Z",
    "updatedOn": null
  }
]
FieldWhat it charges
productCommissionPercentPercentage of the discounted item amount, from 0 to 100.
freightCommissionPercentPercentage of the item's share of shipping. Optional.
fixedFeeFixed amount per order line, in the store currency.

Each product uses the first rule that applies:

OrderRulesource
1The seller's active rule for the product's brand.brand
2The seller's active rule for the category. The closest one wins.category
3commissionPercentage and commissionFixedFee of the category or its closest ancestor.category
4The seller's commissionPercent.seller

A category rule covers subcategories only with includeChildrenCategories: true. The order stores the commission when it's created.

Moderate the products

With manualReview, the seller's new products wait for your decision in draft. List the queue with GET /v1/sellers/received-products (without status, it returns pending) and decide in bulk.

# 1. List the seller's pending products
curl "https://api.ecomiq.pe/api/v1/sellers/received-products?status=pending&sellerId=7204558912004325310&limit=50" \
  -H "Authorization: Bearer $ECOMIQ_TOKEN"

# 2. Approve them
curl -X POST https://api.ecomiq.pe/api/v1/catalog/products/moderation/decide \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "productIds": ["7204558912004325320"],
    "action": "approve",
    "reason": null
  }'
Queue (excerpt)
{
  "data": [
    {
      "id": "7204558912004325320",
      "sellerId": "7204558912004325310",
      "name": "Polo de algodón",
      "price": 59.9,
      "status": "pending",
      "issues": [],
      "reason": null,
      "match": {
        "productId": "7204558912004325302",
        "score": 1,
        "matchedBy": "name",
        "productName": "Polo de algodón"
      },
      "categoryName": "Polos",
      "variantCount": 1,
      "approvalPolicy": "manualReview"
    }
  ],
  "hasNextPage": false,
  "nextCursor": null,
  "totalCount": 1
}
Decision
{ "approved": 1, "rejected": 0 }
statusWhat it includes
pendingWhat the seller sent for review, with no quality issues and no correction request.
issuesWhat the seller sent with quality issues, or a draft with a correction request.
approvedactive.
rejectedinactive with a rejection reason.

Each problem comes in issues:

issuesCause
minImagesIt has fewer images than minImages.
barcodeRequiredAn active variant has no barcode.
priceRequiredAn active variant has a price of 0.
correctionRequestedYou requested a correction.

Set the quality rules in the admin, under To approve → Quality rules.

OperationEffectLimit
POST /v1/catalog/products/moderation/decide with approveMoves to active. Requires zero quality issues.500 products
POST /v1/catalog/products/moderation/decide with rejectMoves to inactive and stores reason, which is required.500 products
POST /v1/sellers/received-products/request-correctionMoves back to draft with reason. Returns requestedCount.100 products
POST /v1/catalog/products/{productId}/seller-offerLinks the product to one of yours and activates it.1 product

reason accepts up to 500 characters. After a correction request or a rejection, the seller's edits don't change the status: approve it when it's ready. With autoApprove, products skip review unless they have a quality issue.

Sell it as an offer

match suggests one of your products with the same SKU, the same barcode, or a similar name. To link them, send that productId as canonicalProductId to Link a seller offer. The target product must be active or outOfStock, belong to the internal seller, and not be an offer itself.

Show the sellers

Storefront's GET /v1/sellers lists active sellers by name, without the internal seller. In a store that isn't a marketplace, it returns 404.

An offer isn't listed on its own: it appears in sellerOffers on your store's product.

curl https://storefront.ecomiq.pe/v1/sellers \
  -H "x-Store-Domain: mi-tienda"

curl https://storefront.ecomiq.pe/v1/products/polo-de-algodon \
  -H "x-Store-Domain: mi-tienda"
Sellers
[
  {
    "id": "7204558912004325310",
    "name": "Textiles Andinos",
    "slug": "textiles-andinos",
    "logo": null,
    "rating": { "average": 4.6, "totalReviews": 18 }
  }
]
Product (excerpt)
{
  "id": "7204558912004325302",
  "name": "Polo de algodón",
  "slug": "polo-de-algodon",
  "seller": {
    "id": "7204558912004325305",
    "name": "Mi tienda",
    "slug": "mi-tienda"
  },
  "sellerOffers": [
    {
      "productId": "7204558912004325320",
      "seller": {
        "id": "7204558912004325310",
        "name": "Textiles Andinos",
        "slug": "textiles-andinos"
      },
      "price": 59.9,
      "compareAtPrice": null,
      "isAvailable": true,
      "totalInventory": 12,
      "variants": [
        { "id": "7204558912004325321", "sku": "TXA-POLO-001", "price": 59.9 }
      ]
    }
  ]
}

Offers are sorted by price. To sell one, add an id from sellerOffers[].variants to the cart.

To showUse
Profile and policiesGET /v1/sellers/{slug}
A seller's productsGET /v1/products?sellers=textiles-andinos
The seller of each productseller and sellerOfferCount in each search result

The sellers filter takes slugs and includes products where the seller has an offer.

Charge a mixed cart

The cart groups its items by seller and shipping profile, and each group becomes an order. Every group with items that ship needs its own shipping method. The buyer pays once.

This cart has one item from Mi tienda and one from Textiles Andinos. To build it, follow Headless store.

curl https://storefront.ecomiq.pe/v1/carts/<cart-token>/shipping-options \
  -H "x-Store-Domain: mi-tienda"
Shipping options (excerpt)
{
  "groups": [
    {
      "sellerId": "7204558912004325305",
      "sellerName": "Mi tienda",
      "shippingProfileId": null,
      "orderAmount": 89,
      "itemIds": ["7204558912004325391"],
      "rates": [
        {
          "shippingMethodId": "7204558912004325381",
          "rateId": "7204558912004325382",
          "name": "Envío regular",
          "type": "regular",
          "target": "automatic",
          "targetId": null,
          "price": 12,
          "currency": "PEN"
        }
      ]
    },
    {
      "sellerId": "7204558912004325310",
      "sellerName": "Textiles Andinos",
      "shippingProfileId": null,
      "orderAmount": 59.9,
      "itemIds": ["7204558912004325390"],
      "rates": [
        {
          "shippingMethodId": "7204558912004325383",
          "rateId": "7204558912004325384",
          "name": "Envío regular",
          "type": "regular",
          "target": "automatic",
          "targetId": null,
          "price": 15,
          "currency": "PEN"
        }
      ]
    }
  ]
}

Pick one method per group, with its sellerId or shippingProfileId. Repeat the call for each group:

curl -X PUT https://storefront.ecomiq.pe/v1/carts/<cart-token>/shipping-method \
  -H "x-Store-Domain: mi-tienda" \
  -H "Content-Type: application/json" \
  -d '{
    "sellerId": "7204558912004325310",
    "shippingProfileId": null,
    "shippingMethodId": "7204558912004325383",
    "rateId": "7204558912004325384",
    "target": "automatic"
  }'

Then check out:

curl -X POST https://storefront.ecomiq.pe/v1/carts/<cart-token>/checkout \
  -H "x-Store-Domain: mi-tienda" \
  -H "Content-Type: application/json" \
  -d '{ "paymentMethodCode": "<payment-method-code>" }'
Checkout (excerpt)
{
  "orderId": "7204558912004325360",
  "orderNumber": "ORD-2026-0101",
  "orderGroup": "7204558912004325360",
  "orderIds": ["7204558912004325360", "7204558912004325361"],
  "payment": {
    "paymentMethodCode": "<payment-method-code>",
    "status": "pending",
    "amount": 175.9
  }
}

orderId and orderNumber belong to the first order. Store orderGroup, which ties together every order from the cart. The buyer sees a single purchase in List customer orders, with its orderIds.

Read the orders by seller

GET /v1/orders/group/{orderGroup} returns the group's orders with their sellerId, and each item carries the commission applied. This example adds up each seller's commission:

curl https://api.ecomiq.pe/api/v1/orders/group/7204558912004325360 \
  -H "Authorization: Bearer $ECOMIQ_TOKEN"
Response (excerpt)
{
  "orderId": "7204558912004325360",
  "orderGroup": "7204558912004325360",
  "orderIds": ["7204558912004325360", "7204558912004325361"],
  "total": 175.9,
  "currencyCode": "PEN",
  "orders": [
    {
      "id": "7204558912004325360",
      "orderNumber": "ORD-2026-0101",
      "sellerId": "7204558912004325305",
      "status": "confirmed",
      "total": 101
    },
    {
      "id": "7204558912004325361",
      "orderNumber": "ORD-2026-0102",
      "sellerId": "7204558912004325310",
      "status": "confirmed",
      "total": 74.9,
      "items": [
        {
          "title": "Polo de algodón",
          "quantity": 1,
          "sellerId": "7204558912004325310",
          "sellerCommissionId": "7204558912004325350",
          "commissionSource": "category",
          "productCommissionPercent": 10,
          "freightCommissionPercent": 5,
          "fixedFee": 1.5,
          "commissionBaseAmount": 59.9,
          "productCommissionAmount": 5.99,
          "freightCommissionBaseAmount": 15,
          "freightCommissionAmount": 0.75,
          "commissionAmount": 8.24
        }
      ]
    }
  ]
}

commissionAmount adds the item commission, the shipping commission, and fixedFee. Use these fields to calculate what you owe each seller; settlement and payouts happen on your side.

Each order in the group has its own status and is fulfilled from its seller's active locations, with that seller's stock. See Fulfillment and delivery.

  • GET /v1/orders includes sellerId and sellerName in each row and filters by seller with sellerIds.
  • order.* events carry sellerId and orderGroup. See Webhooks.

Errors

codeStatusWhat to do
Seller.StoreTypeInvalid400The store isn't a marketplace.
SellerScope.Denied400The product is outside the seller's categories or brands.
Moderation.QualityRequired400Resolve the product's issues before approving it.
ShippingMethods.GroupRequired400The cart has several groups: send sellerId or shippingProfileId.
Orders.ShippingMethodRequired400A cart group has no shipping method.
Seller.SlugExists409Another seller uses that slug: change it.

Next steps

Last updated:

On this page