https://dash.joinmarkt.com with:
{storeId}. Product scopes also require the key owner to have manageProducts on that store.
Successful list, get, create, and update responses are the JSON objects below. They are not wrapped in a data envelope. Errors use this shape:
- 401
Unauthorized, codeUNAUTHORIZED— missing bearer token, unknown key, revoked key, or the account deletion has completed. - 401
API key expired, codeUNAUTHORIZED. - 403
Forbidden, codeFORBIDDEN— the key cannot access this store, the scope is missing, or the owner lacksmanageProducts. - 429
Too many requests, codeRATE_LIMITED— more than 120 requests in 60 seconds from the same IP. - 500 code
INTERNAL_ERROR, pluserrorId.erroris the fallback named on that method.
{productId} in a path is the product id, or a public id PRD- plus 3 to 5 groups of 4 letters or digits (for example PRD-2345-6789-ABCD-EFGH-JKMN). Lookup is case-insensitive and the stored id is uppercased.
A JSON body must be an object. storeId inside the body is removed before validation. The store in the path is the one that is written. Top-level fields other than the ones listed for that method are dropped. Keys inside a variant object are not dropped on create.
List products
product:read
Query:
page— integer, minimum 1, default 1limit— integer, 1–100, default 50
page or limit returns 400 with code BAD_REQUEST. The error text is each Zod issue as page: … or limit: …, joined with ; .
Products are ordered by sortOrder ascending, then createdAt descending. A page past the end returns an empty products array and the real total.
publicId is null when the product has none. stockAvailable here is the number of license keys with isUsed false. Each object in variants is returned with stockKeys removed. Read keys from the stock routes below.
Unexpected failures return 500 Failed to list products.
Create a product
product:create · 201
Required body fields:
name— string, trimmed, 1–200 charactersdescription— string, 1–50000 characters. Create trims it, so a whitespace-only value is stored as""price— number, 0–1000000
slashedPrice— number, 0–1000000, ornullimages— array of strings, max 20 items, each max 500 characters. Omitted images are stored as[]visibility—public,private,unlisted, oron-hold. Omitted values are stored aspubliccategoryId— string, max 100 characters, ornull. The category must belong to this store. On create, a blank string is stored asnullvariants— array of objects. See Variant objectsslug— string, trimmed, 1–200 characters. It is lowercased, runs of other characters become one hyphen, and hyphens at the ends are removed. Omitted slugs are built fromnamethe same way. A taken generated slug gets a-1,-2, … suffix. A slug you send is stored only when that normalized handle is freemetaTitle— string, trimmed, max 200, ornull. A blank string is stored asnullmetaDescription— string, trimmed, max 500, ornull. A blank string is stored asnullmetaImage— string, trimmed, max 500, ornull. A blank string is stored asnullembedCardType—smallorlarge, ornull. Omitted andnullare stored aslargedeliveryConfig— see below. Omitted config is stored as license-key deliverydisplayConfig— see below. Omitted config stores every flag asfalse
deliveryConfig fields:
methods— required array, at least one oflicense_key,download_file,delivery_instructions,custom_fields,manual_deliverydownloadFiles— optional, max 20. Each file hasid(1–100),name(1–200),url(1–500), and optional integersize≥ 0. Required whenmethodsincludesdownload_filemanualDelivery—{ "message": "..." }ornull.messagemax 2000. A non-empty message is required whenmethodsincludesmanual_deliverydeliveryInstructions—{ "content": "..." }ornull.contentis 1–50000 characters. Required whenmethodsincludesdelivery_instructionscustomFields— optional, max 20. Required whenmethodsincludescustom_fields. Each field hasid,label(1–120),identifier(1–64, lowercase letter then letters, numbers, or underscores),type(text,textarea,dropdown,checkbox,radio,number), optionalplaceholder(max 200), optionalhelpText(max 500),required(boolean, defaultfalse), and optionaloptions(max 20 strings, each 1–120)discordRole—{ "guildId": "12345", "roleId": "67890" }ornull. Both ids are 5–32 digitsinstructions— string, max 50000, ornull. A non-empty value is merged intodeliveryInstructionsbefore save
displayConfig booleans, each defaulting to false when the object is present: hideStockCount, liveViewsEnabled, showViewsCount, liveSalesEnabled, showSalesCount, liveSalesNotifications, showSalesNotifications.
Variant objects
On create, each variant is stored with the keys you sent, plusid and publicId. id is kept when it is 8–128 characters after trim; otherwise it is a new UUID. publicId is kept when it is VAR- plus five groups of four characters from 23456789ABCDEFGHJKMNPQRSTUVWXYZ; otherwise a new one is issued. Duplicate publicId values in the same array are re-issued. stockKeys is not returned by list or get. On create it remains in the saved object. It is not inserted as license keys.
Any update rewrites every saved variant, including when variants is omitted and the product already has variants. Sending [] clears them. Each saved object then has only id, publicId, name, description, price, slashedPrice, stock, minQuantity, maxQuantity, volumeDiscounts, disableVolumeDiscountsOnCoupon, and deliveryConfig when the variant had one. Missing name, description, minQuantity, or maxQuantity becomes "". price and slashedPrice become "" when missing or numeric 0. stock is a number, parsed from a numeric string, or 0 when missing. volumeDiscounts is the array you sent, or []. disableVolumeDiscountsOnCoupon is true only for true or "true".
The response is the full product from Get a product. stockAvailable on that object counts keys that are unused and not attached to an order.
Errors:
- 400 code
BAD_REQUEST— invalid JSON (Invalid JSON), a body that is not an object (JSON body must be an object), Zod validation (name: …style, joined with;), orCategory not found in this store - 400 code
product_validation—Slashed price must be greater than regular price - 400 code
product_slug_duplicate—URL handle must use lowercase letters, numbers, and hyphens only.when the normalized handle is empty (including a name that produces no handle), orThis URL handle is already used by another product.when the slug you sent is already taken - 403 code
plan_product_limit, withrequiredPlanandupgradePath/billing— the store is at its product limit.errorisYour {plan} plan allows up to {limit} products. Upgrade to add more. - 403 code
plan_file_delivery_limit, withrequiredPlanandupgradePath/billing—errorisFile "{name}" exceeds the {megabytes} MB delivery limit on your {plan} plan.{name}is the filenamewhen that value is a string, including"". Otherwise it isupload. Product files and variant files are both checked - 500
Failed to create product
Get a product
product:read
deliveryConfig and displayConfig are always objects here. Empty or invalid stored JSON is returned as the create defaults: methods is ["license_key"], the delivery arrays are empty, the delivery objects are null, and every display flag is false. A non-empty stored instructions string is copied into deliveryInstructions when that object was empty, delivery_instructions is added to methods, and instructions is null. stockAvailable counts keys with isUsed false and no order, across variants. variants omit stockKeys. groupId is included here; change group membership with Merchant groups.
Errors:
- 404
Product not found, codeproduct_not_found— no product matches that id - 404
Product not found, codeNOT_FOUND— the product belongs to another store - 500
Failed to load product
Update a product (PATCH)
product:update · 200
Same fields as create. Every field is optional. Omitted fields stay as they are, except variants, which are rewritten as described in Variant objects. null clears slashedPrice, categoryId, metaTitle, metaDescription, metaImage, and embedCardType (embedCardType null is stored as large). A blank metaTitle, metaDescription, or metaImage is stored as null. description is stored as sent.
- 400 code
BAD_REQUEST— invalid JSON, a non-object body, Zod validation (name: …style, joined with;), orCategory not found in this store - 400 code
product_validation— the merged product fails validation.erroris the Zod messages joined with;, without field paths - 400 code
product_slug_duplicate— the same URL-handle messages as create - 400 code
product_license_keys_required— when the variants array that is saved is empty and delivery includeslicense_key, with no unused keys that have no order:At least one license key must be added before customers can purchase this product.When that array is not empty, the product-level check is skipped. A variant is checked only when its name is non-empty and itsdeliveryConfig.methodsincludeslicense_key. The message is{variant name}: add at least one license key before customers can purchase this variant.Non-blank lines in that variant’sstockKeysstring satisfy the check. When there are no such lines, unused keys with no order for that variant name are counted instead.stockKeysis removed before the variant is saved and those lines are not inserted as license keys. A variantdeliveryConfigthat fails validation is 500Failed to update product, not this error - 403 code
plan_file_delivery_limit, withrequiredPlanandupgradePath/billing— sameerrortext as create - 404 — same product-not-found cases as get
- 500
Failed to update product
Update a product (PUT)
product:update · 200
PUT calls the same update as PATCH. Send the same partial object.
Failed to update product.
Delete a product
product:delete · 200
No body. When every license key is unused and not attached to an order, the product and those keys are deleted.
- 400 code
product_has_fulfilled_keys—Cannot delete a product that has delivered license keys. Hide it instead.This is returned when any key hasisUsedtrue or an order id - 404 — same product-not-found cases as get
- 500
Failed to delete product
List stock
product:stock:read
Optional query variantName. Omit it, or send a blank value, to read keys whose variant name is null. A non-blank value filters to that variant. This does not list keys that are already attached to an order.
productId is the path value you sent. count is keys.length. A key can be listed with isUsed true when it has no order.
Errors:
- 404
Product not found, codeNOT_FOUND - 500
Failed to load stock keys
Replace stock
product:stock:write · 200
Deletes keys for that variant that are not attached to an order, then stores the keys you send. Keys that are only whitespace are dropped. Kept keys are trimmed. variantName null or omitted targets keys whose variant name is null.
Body:
keys— required array of stringsvariantName— optional string ornull
count is how many non-blank keys were stored.
Errors:
- 400 code
BAD_REQUEST— invalid JSON, a non-object body, or Zod validation ofkeys/variantName(issue messages joined with;) - 404
Product not found, codeNOT_FOUND - 500
Failed to replace stock keys
Append stock
product:stock:write · 200
Adds keys. Existing keys stay. Whitespace-only strings are dropped. Kept keys are trimmed. Same body as replace.
added is how many non-blank keys were inserted. count is how many unused keys with no order remain for that variant afterward.
Errors:
- 400 code
BAD_REQUEST— invalid JSON, a non-object body, or Zod validation - 404
Product not found, codeNOT_FOUND - 500
Failed to append stock keys

