Consignify Documentation

Consignify API v1

RESTful API for managing consignors, products and payouts in your Shopify store.

Authentication

All requests require a Bearer token generated in the Consignify app settings. Include it in the Authorization header:

Authorization: Bearer <api_key>
Base URL
https://consignify.shop/api/v1
Endpoints

Consignors

GET/consignors

List the shop's consignors that aren't archived. The list isn't paginated.

Response:
[ { "id": "w13v…", "givenName": "Ludwig", "surname": "van Beethoven", "key": "Beethoven" }, { "id": "vr0z…", "givenName": "Galileo", "surname": "Galilei", "key": "Galileo" } ]

Consignor fields

POST /consignors and PATCH /consignor/{id} accept these fields. When you create a consignor, the fields you leave out are empty or use the shop default, and archived is false. When you update one, the fields you leave out keep their values.

  • key: your unique code for the consignor. Required when creating.
  • givenName, surname, notes: text, or null to clear.
  • email: an email address no other consignor in the shop uses, or null to clear.
  • phone: a phone number with at least 5 digits, or null to clear.
  • share: the consignor's share as a percentage (0–100). null clears it, so the shop default applies. A product's own share override takes precedence.
  • unsoldItemFate: SHOP_DEFAULT, DONATE or RETURN.
  • archived: true or false. Archiving a consignor who has active products returns 409.
  • portalEnabled, productEntryEnabled, activeProductEditEnabled: SHOP_DEFAULT, ENABLED or DISABLED. They control portal access, adding products from the portal, and editing active products from the portal.
  • productLocationId: the Shopify location where the consignor manages inventory in the portal, as gid://shopify/Location/N. null, an empty string or SHOP_DEFAULT uses the shop default.
  • merchantAbsorbsDiscount: true to calculate this consignor's commission on the price before discounts, false to use the discounted price, or null for the shop default.
  • locale: the language for emails sent to the consignor, as a language tag such as fr or de-CH (up to 10 characters). null uses the shop's language.
POST/consignors

Create a consignor with the fields above. Only key is required. Responds 201 with the new consignor. A key or email that another consignor already uses returns 400.

Request Body:
{ "key": "test-123", "email": "friend@example.com", "unsoldItemFate": "DONATE", "merchantAbsorbsDiscount": false }
Response:
{ "id": "c804crn7lgesjc1fshf3b0bb", "key": "test-123", "shopId": "efrus-test-store3.myshopify.com", "givenName": null, "surname": null, "share": null, "email": "friend@example.com", "phone": null, "notes": null, "unsoldItemFate": "DONATE", "createdAt": "2025-07-31T16:42:00.000Z", "updatedAt": "2025-07-31T16:42:00.000Z", "archived": false, "portalEnabled": "SHOP_DEFAULT", "productEntryEnabled": "SHOP_DEFAULT", "activeProductEditEnabled": "SHOP_DEFAULT", "productLocationId": null, "merchantAbsorbsDiscount": false, "locale": null }
GET/consignor/{id}

Get a single consignor by id.

Response:
{ "id": "w13vmscy6j5uub1taivtmp5i", "key": "Beethoven", "shopId": "efrus-test-store3.myshopify.com", "givenName": "Docs", "surname": "Sample", "share": null, "email": "hello+beethoven@consignify.shop", "phone": null, "notes": null, "unsoldItemFate": "DONATE", "createdAt": "2025-07-31T15:53:56.499Z", "updatedAt": "2025-07-31T16:40:30.883Z", "archived": false, "portalEnabled": "SHOP_DEFAULT", "productEntryEnabled": "SHOP_DEFAULT", "activeProductEditEnabled": "SHOP_DEFAULT", "productLocationId": null, "merchantAbsorbsDiscount": null, "locale": null }
PATCH/consignor/{id}

Update a consignor with the fields above. Only the fields you send change. Archiving a consignor who still has active products returns 409.

Request Body:
{ "givenName": "Docs", "surname": "Sample" }
Response:
{ "id": "w13v…", "key": "Beethoven", "shopId": "efrus-test-store3.myshopify.com", "givenName": "Docs", "surname": "Sample", "share": null, "email": "hello+beethoven@consignify.shop", "phone": null, "notes": null, "unsoldItemFate": "DONATE", "createdAt": "2025-07-31T15:53:56.499Z", "updatedAt": "2025-07-31T16:40:30.883Z", "archived": false, "portalEnabled": "SHOP_DEFAULT", "productEntryEnabled": "SHOP_DEFAULT", "activeProductEditEnabled": "SHOP_DEFAULT", "productLocationId": null, "merchantAbsorbsDiscount": null, "locale": null }
GET/consignor/{id}/products

All products associated with this consignor.

Response:
[ { "id": "14985358311805", "title": "The 3p Fulfilled Snowboard", "status": "ACTIVE", "totalInventory": 19, "consignerShare": null, "shopifyUpdatedAt": "2025-07-28T17:58:04.000Z" }, { "id": "14985358082429", "title": "The Videographer Snowboard", "status": "ACTIVE", "totalInventory": 46, "consignerShare": null, "shopifyUpdatedAt": "2025-07-28T17:58:05.000Z" } ]
POST/consignor/{id}/products

Assign products to this consignor. Send an array of numeric Shopify product ids (numbers or numeric strings, not GIDs). An invalid body or an archived consignor returns 400, and an unknown consignor 404. Otherwise the response is always 202 and streams plain-text progress lines. A product that fails appears only as an Error processing product line, and the last line reads Completed with N errors or All products processed successfully.

Request Body:
[ 14985358082429, 14985358016893 ]
Response:
Started processing 2 products... Processed product 14985358082429 (1/2) Error processing product 14985358016893: Product not found on Shopify: 14985358016893 Completed with 1 errors
GET/consignor/{id}/payouts

List this consignor's payouts, paid and unpaid (an unpaid payout has paidAt null). An unknown consignor id returns an empty list.

Response:
[ { "id": "ovfjhrqu1e6on4eta4wqq6sw", "shopId": "efrus-test-store3.myshopify.com", "reference": "#R1001", "amount": 1067.94, "adjustment": 0, "currencyCode": "GBP", "paidAt": "2025-07-31T15:55:01.660Z", "createdAt": "2025-07-31T15:55:01.662Z", "note": "", "updatedAt": "2025-07-31T15:55:03.624Z", "consignerId": "w13vmscy6j5uub1taivtmp5i", "receiptSentAt": "2025-07-31T15:55:03.623Z", "receiptStatus": "SENT", "receiptStatusReason": null } ]

Products

GET/products?limit=100&cursor=<optional>

Paginated list of products.

Response:
{ "data": [ { "id": "14985357918589", "updatedAt": "2025-07-31T15:54:23.392Z", "shopId": "efrus-test-store3.myshopify.com", "consignerId": null, "shopifyUpdatedAt": "2025-07-28T17:58:05.000Z", "consignerShare": null, "status": "ACTIVE", "totalInventory": 8, "title": "The Compare at Price Snowboard" },...], "nextCursor": "14985…" }
GET/product/{id}

Single product details.

Response:
{ "id": "14985357918589", "updatedAt": "2025-07-31T15:54:23.392Z", "shopId": "efrus-test-store3.myshopify.com", "consignerId": null, "shopifyUpdatedAt": "2025-07-28T17:58:05.000Z", "consignerShare": null, "status": "ACTIVE", "totalInventory": 8, "title": "The Compare at Price Snowboard" }
GET/product/{id}/consignor

The consignor assigned to this product. Returns null when the product has no consignor or isn't found.

Response:
null | { id, key, givenName, … }
POST/product/{id}/consignor

Assign the product to a consignor by key or id. Send { "key": null } or { "id": null } to remove its consignor. Responds 201 with an empty body. Errors: 400 for an invalid product id or body, or an archived consignor; 404 when the consignor isn't found, or when assigning a product Shopify doesn't have; 500 when the update fails.

Request Body:
{ "key": "Beethoven" }
Response:
HTTP 201 (empty body)
GET/product/{id}/share

The product's own share override, as a percentage. null means the product has no override and uses the consignor's share or the shop default.

Response:
{ "share": 50 }
POST/product/{id}/share

Set the product's share override (0–100). Send { "share": null } to remove it, so the consignor's share or the shop default applies. Responds 201 with an empty body. Errors: 400 for an invalid product id or body; 404 when setting a share on a product Shopify doesn't have; 500 when the update fails.

Request Body:
{ "share": 45 }
Response:
HTTP 201 (empty body)
POST/products/fields

Set the consignor, the share override, or both on up to 250 products in one request. Responds 200 with the number of products and an entry for each product that wasn't updated; retryable is true when sending that product again can succeed. Errors: 400 for an invalid body or an archived consignor; 404 for an unknown consignor; 413 for more than 250 products.

Request fields:
  • ids: numeric Shopify product ids (numbers or numeric strings), at least one and at most 250. Duplicates count once.
  • consignerId: a consignor id, or null to remove the products' consignor.
  • share: a number from 0 to 100, or null to remove the products' share override. Send consignerId, share or both; a field you leave out is unchanged.
Request Body:
{ "ids": [ 14985358082429, 14985358016893 ], "consignerId": "w13vmscy6j5uub1taivtmp5i", "share": 45 }
Response:
{ "total": 2, "failed": [ { "id": "14985358016893", "error": "This shop is busy with another change. Please try again in a moment.", "retryable": true } ] }
GET/product/{id}/items?limit=100&cursor=<optional>

Paginated order line-items for this product.

Response:
{ "data": [ { "id": "34838143369597", "orderId": "11725356106109", "productId": "14985357918589", "variantId": "55263182029181", "sku": null, "originalQuantity": 1, "currentQuantity": 1, "unitPrice": 785.95, "unitPriceDiscounted": 785.95, "currencyCode": "GBP", "isFulfilled": true, "fulfillmentCreatedAt": "2025-03-04T11:24:56.000Z", "fulfillmentLastUpdatedAt": "2025-03-04T11:24:56.000Z", "itemPayout": null }, ...], "nextCursor": null }
GET/product/{id}/payouts

Payout line-items for this product.

Response:
[] | [ { id, commissionRate, commissionRateType, quantity, amount, payout: { id, reference } } ]

Payouts

GET/payouts?limit=100&cursor=<optional>

Paginated list of the shop's payouts, sorted by reference as text (so #R1000 comes before #R999). The cursor is a payout reference: pass the previous page's nextCursor. References start with #, so URL-encode the cursor (cursor=%23R1001).

Response:
{ "data": [ { "id": "ovfj…", "reference": "#R1001", … } ], "nextCursor": null }
POST/payouts

Create a payout for a consignor. The payout amount and each line's amount are stored as you send them and aren't recalculated, so send values that add up. The payout is marked paid now unless you send "unpaid": true. Responds 201 with the payout, its sale lines (payoutLineItems) and its additional items (customLineItems).

Request fields:
  • consignerId: the consignor's id. Required.
  • amount: the payout total. Required.
  • lineItems: the sales to pay out, in the format below. Required; may be empty. A sale can be in only one payout.
  • customLineItems: additional items, each { "description": "…", "amount": 10 }. Both are required; use a negative amount for a deduction such as a fee. Optional.
  • note: an optional note.
  • adjustment: an optional adjustment amount, or null for none. It's stored as sent; include it in amount yourself.
  • unpaid: true to create the payout unpaid. Defaults to false.
  • sendReceipt: accepted but ignored. The API doesn't email receipts; you can send one from the payout in the Consignify admin.
Each lineItems entry:
  • id: the sale's order line item id, as returned by /product/{id}/items.
  • quantity: the number of units paid out, a whole number of at least 1.
  • commissionRateType: PERCENT, FIXED, PERCENT_OF_NET or PERCENT_MINUS_COST.
  • commissionRate: a percentage from 0 to 100, or an amount for FIXED.
  • amount: what the consignor is paid for this line, 0 or more.
  • fulfilledAt: when the sale was fulfilled, as an ISO 8601 date and time.
  • salesTotalAtPayout, salesTotalBeforeDiscountAtPayout: the sale's total after and before discounts.
  • costBasis: required for PERCENT_OF_NET and PERCENT_MINUS_COST, otherwise optional. The net price for PERCENT_OF_NET, the total cost for PERCENT_MINUS_COST.

Errors: 400 for an invalid body (JSON errors); 404 for an unknown consignor; 409 when a sale is already in another payout, belongs to another consignor or none, was marked settled, or no longer exists; 503 when the shop is busy.

Request Body:
{ "consignerId": "w13vmscy6j5uub1taivtmp5i", "amount": 466.57, "lineItems": [ { "id": "34838143369597", "quantity": 1, "commissionRate": 60, "commissionRateType": "PERCENT", "amount": 471.57, "fulfilledAt": "2025-03-04T11:24:56.000Z", "salesTotalAtPayout": 785.95, "salesTotalBeforeDiscountAtPayout": 785.95 } ], "customLineItems": [ { "description": "Cleaning fee", "amount": -5 } ], "note": "Bank transfer", "unpaid": true }
Response:
{ "id": "lsl…", "reference": "#R1003", "amount": 466.57, "adjustment": null, "paidAt": null, "note": "Bank transfer", "payoutLineItems": [ { "id": "34838143369597", "quantity": 1, "commissionRate": 60, "commissionRateType": "PERCENT", "amount": 471.57, …, "orderLineItem": { … } } ], "customLineItems": [ { "id": "…", "description": "Cleaning fee", "amount": -5, … } ], … }
GET/payout/{id}

Get payout details including line items.

Response:
{ "id": "lsl…", "reference": "#R1003", "payoutLineItems": [ { "id": "…", "amount": 0, "orderLineItem": { … } } ], "customLineItems": [ { "id": "…", "description": "Bonus", "amount": 10 } ] }
PUT/payout/{id}

Replace the content of an unpaid payout. The body takes the same fields as creating one, without consignerId, and replaces the payout as a whole. Paid payouts can't be edited (409).

  • lineItems is required and lists every sale the payout should contain; a sale you leave out is removed. Each entry needs all the line fields. A sale already in the payout keeps its saved line whatever you send for it, so to change a line, remove the sale and add it again. Only added sales take your values and must meet the line rules.
  • Leaving out customLineItems deletes all the payout's additional items.
  • The amount is recalculated from the lines, additional items and adjustment; an amount you send is ignored. Leaving out adjustment keeps the current one, and null clears it.
  • note must be text: null is rejected. Leaving it out keeps the current note. To clear the note, use PATCH.
  • The payout is marked paid unless you send "unpaid": true. sendReceipt is ignored. Errors are as for creating a payout, plus 404 for an unknown payout and 409 for a paid one.
Request Body:
{ "lineItems": [ { "id": "34838143369597", "quantity": 1, … } ], "customLineItems": [ { "description": "Bonus", "amount": 10 } ], "note": "Adjusted total", "adjustment": 0, "unpaid": true }
Response:
{ "id": "lsl…", "reference": "#R1003", "amount": 481.57, "adjustment": 0, "paidAt": null, "note": "Adjusted total", "payoutLineItems": [ { "id": "34838143369597", "amount": 471.57, …, "orderLineItem": { … } } ], "customLineItems": [ { "id": "…", "description": "Bonus", "amount": 10, … } ], … }
PATCH/payout/{id}

Partially update a payout. "note" works on both paid and unpaid payouts (send an empty string or null to clear it). "paid": true marks an unpaid payout as paid; sending it on an already-paid payout returns 409.

Request Body:
{ "note": "Paid via bank transfer", "paid": true }
Response:
{ "id": "lsl…", "reference": "#R1003", "note": "Paid via bank transfer", "paidAt": "2025-07-31T…", "payoutLineItems": [ … ], "customLineItems": [ … ] }
DELETE/payout/{id}

Delete payout.

Response:
HTTP 204 (no content)
Error Handling

The API uses standard HTTP status codes. Most error responses are a plain-text message. When the body of a request that creates or updates a consignor or payout fails validation, the response is JSON instead, with an errors object that lists the messages for each field:

{ "errors": { "email": [ "Please enter a valid email address" ] } }
201Created – the change was saved (most successful POST requests)
202Accepted – processing continues and progress is streamed as text (POST /consignor/{id}/products)
400Bad Request – Invalid request data
401Unauthorized – Invalid, revoked or expired API key
404Not Found
405Method Not Allowed
409Conflict – the request conflicts with current data: the payout is already paid, a sale is in another payout or can no longer be paid out, or the consignor to archive has active products
413Payload Too Large – more than 250 products in one POST /products/fields request
500Internal Server Error
503Service Unavailable – the shop is busy with another change; retry shortly