Stores: inventory sync
Listing a shop’s stock from a CSV or one book at a time.
Access
Store routes take the store’s id in the path (/api/v1/stores/{storeId}/…) and a member of that store as the caller. Today that’s a signed-in member’s session; store API keys, scoped to one store, are coming with ENGIN-45.
Importing stock from a CSV
Two calls: a preview, which saves nothing, and a commit of the rows you’ve checked.
- Preview an inventory CSV: send the file’s text as
csv. Headers are recognised loosely (“Series” or “Title”, “Issue #” or “No.”, “Price”, “Qty”…); a title, an issue number and a price column are required. Each row comes back with its parsed fields, its problems, and amatch:matched,ambiguous,not_foundorinvalid, with candidates. - Import inventory rows: send the rows to list, each with the catalog
issueId(andeditionIdwhen known) you chose. It’s idempotent, so a retry can’t list a row twice.
import { createCollectorClient } from '@collector/sdk';
const collector = createCollectorClient({ token: process.env.COLLECTOR_TOKEN });
const storeId = '00000000-0000-4000-8000-000000000000';
// 1. Preview: the API reads the CSV and matches each row to the catalog. Nothing is saved.
const csv = ['title,issue,price,qty', 'Saga,1,24.99,2', 'The Amazing Spider-Man,300,450.00,1'].join('\n');
const preview = await collector.previewInventoryImport({ path: { storeId }, body: { csv } });
// 2. Keep the rows that matched one book; check the rest by hand.
const rows = preview.rows.flatMap((row) => {
const { match } = row;
const pick = match.candidates.find((c) => c.key === match.chosenKey) ?? match.candidates[0];
if (match.status !== 'matched' || !pick?.issueId || row.errors.length) {
console.warn(`line ${row.line}: ${match.status}`, row.errors.join(' '));
return [];
}
return [
{
line: row.line,
issueId: pick.issueId,
editionId: match.editionId ?? pick.editionId,
fields: row.fields,
},
];
});
// 3. Commit them. The client sends an Idempotency-Key, so a retry can't list them twice.
const { created, failed } = await collector.commitInventoryImport({ path: { storeId }, body: { rows } });
console.log(`${created} listed, ${failed.length} failed`);One listing at a time
- Find a book to list, then List a book with its price in cents, quantity, condition or grade, and status.
- List the store’s inventory filters by status and text, and pages by
limitandoffset. - Update a listing for price and stock changes; Set many listings’ status to hide or restore many at once.
Holds and questions
Collectors ask a store to hold a book, or ask a question about one. The store answers with Accept or decline a hold request, records the sale with Record the sale, and replies in a question’s thread with Reply in a question’s thread. Webhooks for new holds and questions are planned (Webhooks); until then, poll List the store’s holds.
Next: Admin: scripting safely