Lesson 3 Trick: Roll Over ~20 minutes

Handling Errors

Break the load deliberately, find out why, fix the data, and reprocess the failures

Step 1 — Get the File

Download inventory-errors.csv

A thousand inventory records, and a handful of them are deliberately broken. Some point at products and locations that don’t exist. Others carry values the API won’t accept. That is not a contrived exercise — it is what a real extract looks like the first time you load it.

Step 2 — Load It

Open the Puppy School — Bulk Inventory Upload endpoint you built in Lesson 2 and click Run Batch.

In Source, drop the file onto the upload area and click Import Data.

Run Batch walks forward by itself. All eight columns match the template’s variables by name, so it passes Map. Then it stops on Validate: something in this file needs you.

Step 3 — Fix What Dobermann Caught

The step bar says Validate: 1 row needs fixing, and the grid shows just that row: row 999, SKU-WOOF-006-ERR, with a quantity of -50 in an amber cell. Filter Errors is already on, hiding every row that is fine; the button reads Show All.

Dobermann knows stock cannot be negative because you told it, back in Lesson 2:

"quantityOnHand": "{{quantityOnHand:number|>=0}}"

That is also why the column header carries a dotted underline — hover it and Dobermann names the rule.

Click the cell, change -50 to 50, and click Next. The check passes and you land on Review. Your file on disk is untouched; the edit applies to this run.

Step 4 — Send It

On Review, set Rows per request to 10; the requests rebuild as you type. A thousand records go out as a hundred requests of ten.

On Execute, check Error Handling is on Continue processing. Stop on first error would abandon the whole run at the first bad request.

Hit Execute. Ninety-nine requests succeed. One fails.

Step 5 — One Error for Ten Records

Nine hundred and ninety records are in. The Error tab holds a single row:

{
  "error": "new row for relation \"playground_inventory\" violates check constraint \"playground_inventory_uom_check\"",
  "code": "INSERT_ERROR"
}

The API validates the array and rejects it as a unit, so one bad record takes the nine around it down with it — and names only itself.

So you know a uom is wrong somewhere in the last ten rows. You don’t know which row. You don’t know whether it’s the only one.

Step 6 — Narrow It Down

Don’t set Rows per request to 1 and run the thousand again. Nine hundred and ninety records went in on ninety-nine requests, and that speed is worth keeping. Open up the one request that failed instead.

In the Console footer click Reprocess, choose Split array errors, Continue, then Split. Every element of the failed array becomes a transaction of its own.

One goes through. Nine fail — not one. Each carries its own error, against its own record.

Run coarse, split on failure. You get the speed of big requests and the precision of small ones, and you only pay for precision on the records that earned it.

Step 7 — Read the Errors

The Error tab now holds nine rows. It is a table, and two of its columns carry the story — error and code:

{
  "error": "Invalid reference value: Key (gtin)=(99999999999999) is not present in table \"playground_products\". Use GET /reference/<type> to see valid values.",
  "code": "FK_VIOLATION"
}

Read the message, not just the code — it names the column and the value that is missing. Right-click any row → View transaction for the whole exchange, request and response side by side. And if your Error tab is showing different columns from these, a saved View is choosing them; Lesson 4 is where you build your own.

The nine split into two kinds, and this is the distinction that matters most in the course:

  • FK_VIOLATION — five records pointing at a GTIN or location GLN that doesn’t exist in the reference tables. The records are fine; the master data is missing. You can fix these.
  • INSERT_ERROR — four records carrying values the API rejects outright: a uom of BOXES or PALLETS when only EA, CS, PL and KG are allowed, and a status of expired or deleted. Bad data at source.

Five you can clear yourself in five minutes. Four go back to whoever produced the file.

Step 8 — Add One Product

Reference records you add are yours alone — every learner gets their own view of the reference tables, so you can add, break and fix freely.

Create a new endpoint and hit Run API:

// Name: Add Missing Product
// Method: POST
// Path: /reference/products

{
  "gtin": "99999999999999",
  "sku": "SKU-FIX-001",
  "description": "Invisible Cat Repellent",
  "unitPrice": 9.99
}

One product, created through the API.

Step 9 — Turn That Into a Batch

Three more GTINs are missing. Convert the endpoint you already have rather than writing three more.

Open Add Missing Product, put your cursor on the "gtin" line and press Ctrl+M:

"gtin": "{{gtin:string}}", //99999999999999

Dobermann replaced the hardcoded value with a variable, named it from the key, typed it from the value, and kept the original in a comment. Do the same for every line:

{
  "gtin": "{{gtin:string}}", //99999999999999
  "sku": "{{sku:string}}", //SKU-FIX-001
  "description": "{{description:string}}", //Invisible Cat Repellent
  "unitPrice": "{{unitPrice:number}}" //9.99
}

It worked out unitPrice:number on its own, from the value. The footer has grown a Run Batch button beside Run API.

Save, click Run Batch, and in Source switch to the Paste Text tab. Paste this and click Import Data — no file needed:

gtin,sku,description,unitPrice
00000000000000,SKU-FIX-002,Quantum Fetch Ball,14.99
11111111111111,SKU-FIX-003,Self-Walking Leash,29.99
55555555555555,SKU-FIX-004,Teleportation Dog Bed,199.99

Run it. Three products, one execution.

Now the missing locations. Same pattern — a new endpoint:

// Name: Add Missing Location
// Method: POST
// Path: /reference/locations

{
  "gln": "9999999999999",
  "name": "Mystery Warehouse"
}

Ctrl+M each line, save, then Run Batch → Paste Text with this:

gln,name
9999999999999,Mystery Warehouse
0000000000000,Nonexistent Depot
8888888888888,Ghost Distribution Center

Run it. Any endpoint becomes a batch endpoint the moment its values become {{variables}}.

Step 10 — Reprocess

Open History and open the failed inventory batch. In the Console footer click Reprocess, choose Errors only, then Continue. It reprocesses the nine transactions the split left behind, not all ten.

Five clear. The products and locations they pointed at now exist.

Four fail again, and that is the correct outcome — BOXES, PALLETS, expired and deleted are still exactly as wrong as they were ten minutes ago.

Look at SKU-BAD-004. It had a missing GTIN and an invalid uom of PALLETS. You added its product in Step 9 and it still fails, because it had a second problem that master data was never going to solve. Always re-read the error after a reprocess instead of assuming your fix worked.

Step 11 — Find the Rows

The error names the constraint. It does not name the record — so before you can fix anything you have to work out which four rows these are.

Open the Input tab. It holds every row you sent, exactly as the file had it, and a Result column: what happened to the request that sent each row. Set Result to Error.

Four rows are left, and one of them you have already met:

SKU-WOOF-003-ERR    uom BOXES
SKU-BAD-004         uom PALLETS
SKU-WOOF-004-ERR    status expired
SKU-WOOF-005-ERR    status deleted

Right-click one → View transaction to see the request that carried it and the error beside it.

No Result column? Sort.

Click a column header and the odd values fall out of line: BOXES and PALLETS sort clear of EA, CS and PL. The cheapest diagnostic you have, on any table.

Step 12 — Fix It at Source

You cannot decide what BOXES was supposed to mean. Neither can Dobermann. That is a question for whoever produced the extract, and it is the one part of this lesson no tool does for you — so you ask. With Result still on Error, click Export → Excel: the Input sheet holds just those four rows. That’s what you send back.

The answer comes back: BOXES is a case, PALLETS is a pallet, and both expired and deleted mean the line is discontinued.

Now you can fix it. Open your inventory endpoint, click Run Batch, switch to the Paste Text tab and paste the four corrected records:

gtin,sku,description,locationGln,locationName,quantityOnHand,uom,status
00012345600036,SKU-WOOF-003-ERR,Squirrel Detection Radar,0614141000036,German Shepherd Sorting Facility,150,CS,active
00012345600043,SKU-WOOF-004-ERR,Anti-Mailman Defense System,0614141000043,Poodle Processing Center,80,EA,discontinued
00012345600050,SKU-WOOF-005-ERR,Automatic Treat Dispenser Pro,0614141000050,Bulldog Bulk Warehouse,25,EA,discontinued
55555555555555,SKU-BAD-004,Teleportation Dog Bed,0614141000029,Labrador Logistics Hub,100,PL,low_stock

Four records, four successes, and nothing left in the Error tab.

Ten broken records, then. One you caught before sending anything and nine the API caught — five of those you fixed with master data you could legitimately create, and four you could only fix by going back to the source and asking. Load, fail, diagnose, fix, reprocess. That loop is most of what a data migration actually is.

🐾 Dobermann Philosophy

Errors are not failures — they are information. Dobermann surfaces exactly what went wrong, against which record, with enough detail to act on. The reprocess flow exists because in a real migration, fixing and rerunning individual failures is the job. Dobermann makes that precise and repeatable, instead of a spreadsheet full of guesswork.

🦴 Dig Deeper

FK_VIOLATION is a foreign key constraint error: a value in your data points at a record that doesn’t exist in a related table. Databases use foreign keys to guarantee referential integrity — no inventory against a product nobody has ever heard of. MDN’s HTTP response codes reference covers the 400-range errors you’ll meet working with any REST API.

Check My Homework

To pass: A failed POST /inventory followed by a later successful POST /inventory