Centering from a photo to person-reviewed lines
Build a two-step centering flow: measure one face, let a person correct corners and lines, then request centering-only grade ceilings.
For collection apps, marketplaces and grading tools. Growth and higher include centering; Developer does not. A verified Free trial includes 5 measure calls and 5 confirm calls, independently. Grade and standards are available during the verified trial without separate call counters. Costs: measure 25, confirm 5, grade 1, standards 1 credits. Every route requires vision:read.
Photo of one face → measure → display ratios, uncertainty and geometry
↓ corners wrong?
person corrects corners → measure again
↓
person checks/moves cut and print lines
↓
confirm (confirmed_by: "person")
↓
centering-only ceilings + client-confirmed-lines1. Measure and draw the geometry
Use measure with a multipart image and the actual face (front or back). Choose standard or small card_size. needs-review with null ceilings is a normal HTTP 200 result; show the ratios and uncertainty when present and ask for line review. A status of failed means the photo could not produce a reliable measurement. Geometry may be absent; then retake the photo before offering confirmation.
curl -s "https://api.pokemontcgapi.com/v1/centering/measure" \
-H "X-Api-Key: $PTCG_API_KEY" \
-F [email protected] -F face=front -F card_size=standard > measure.json- Show the photo, card corners, cut lines and print lines. Show L/R and T/B ratios with uncertainty_pp, confidence and warnings.
- Use a clear photo of the whole card, straight from above, on a dark flat background, without a sleeve. For borderless fronts, measure the back.
- Keep the measured face and card_size throughout the flow. The other face can lower the ceiling.
This Japanese Koiking / Magikarp Art Rare (sv1a 080/073) was photographed with a phone on dark cloth and cropped around the card before upload. The uploaded crop had a longest edge of 2506 px. Here is the geometry returned by measure on 17 October 2026.

face: front and card_size: standard, measure returned HTTP 200, status: needs-review, code: HIGH_UNCERTAINTY, L/R 50/50 ±15.1 pp, T/B 51/49 ±17.3 pp, confidence 0.68 and no warnings. Red circles mark corners, cyan lines mark the cut and yellow lines mark the print. After a person reviewed the lines, confirm returned L/R 50/50 ±1.7 pp and T/B 51/49 ±1.7 pp, with basis: client-confirmed-lines and ceilings of PSA 10 (official), BGS 10 (interpreted), CGC 10 (interpreted) and SGC 10 (approximate). A more distant photo of this same front, with the card small in the frame, had uncertainty of about 24 pp and a misplaced print line; no warning was emitted. Cropping around the card before upload fixed both issues. This is a ceiling from centering alone, not a prediction of the final grade.
sv1a 080/073), photographed with a phone on the same dark cloth. The full 2252×4000 px photo was uploaded without cropping and reduced by the engine to 1441×2560 px. The card was small in the frame, which accounts for the high uncertainty. The image shown here is cropped around the card only for presentation; measure used the full photo. With face: back and card_size: standard, measure returned HTTP 200, status: needs-review, code: HIGH_UNCERTAINTY, L/R 52/48 ±23.4 pp, T/B 52/48 ±25.7 pp, confidence 0.35, no warnings and geometry. After a person reviewed the lines, confirm with confirmed_by: person returned L/R 52/48 ±1.6 pp and T/B 52/48 ±1.6 pp, with basis: client-confirmed-lines and ceilings of PSA 10, BGS 10 and CGC 10. The SGC ceiling was null because SGC has no published back table. Grade with both faces returned 10 for PSA, BGS, CGC and SGC. This is a ceiling from centering alone, not a prediction of the final grade.Coordinates and corrected corners
geometry_version is 1. geometry.image and geometry.corners use image pixels after EXIF orientation and reduction to at most 2560 px on the longest edge. Corners are top-left, top-right, bottom-right, bottom-left. Display the EXIF-oriented photo and scale overlay coordinates to its displayed dimensions. Send corners in this processed coordinate space, not original camera pixels.
If the corners are wrong, repeat measure with corrected corners. In multipart, corners is a serialized JSON array of four {x,y} points. Corrected corners do not assert that a person has reviewed the cut and print lines.
// form is the multipart body from the measure example.
form.set("corners", JSON.stringify(correctedCorners));
// Repeat measure with the same face and card_size, then render its new geometry.geometry.lines.cut and geometry.lines.print use rectified coordinates including padding. Vertical sides use x = intercept + slope*y; horizontal sides use y = intercept + slope*x. Draw in the rectified view, or map that view back onto geometry.corners using a projective transform. The line editor guide gives the formulas and a tested reference implementation. Account for rectified.corner_offset: corner (i + corner_offset) % 4 maps to canonical cut corner i before rectified.rotations clockwise quarter-turns. Preserve the full geometry, including dimensions, warnings and positioning_sigma when editing lines.
2. Let a person review, then confirm
Build the review controls with the line editor guide: corner correction, line dragging, validation and the confirm payload.
Only after a person explicitly accepts or adjusts the displayed lines should your app call confirm. A successful result has basis: client-confirmed-lines. This is a client assertion about the review in your app. Never label the result verified or certified.
// measurement is the preceding measure response.
// Connect this handler to the person's explicit acceptance in your line editor.
async function onPersonAccepted(reviewedGeometry) {
const response = await fetch("https://api.pokemontcgapi.com/v1/centering/confirm", {
method: "POST",
headers: { "X-Api-Key": process.env.PTCG_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ face: measurement.data.face, card_size: measurement.data.card_size,
geometry: reviewedGeometry, confirmed_by: "person" }),
});
if (!response.ok) throw new Error(JSON.stringify(await response.json()));
const { data } = await response.json();
console.log(data.ceilings, data.basis, data.disclaimer);
}Always show the limitation
ceiling from centering alone, not a prediction of the final grade. Show this next to the PSA/BGS/CGC/SGC ceilings. Surface source_status and borderline; interpreted or approximate thresholds are not official company promises. Confirm returns ratios and ceilings, not another geometry; keep the geometry reviewed in your app.
Known ratios and standards
Use grade for ratios already known from a measurement: for example {"front_lr":"55/45","front_tb":52}. Do not guess missing faces or axes. Read incomplete, missing_faces and missing_axes. Standards lists source URLs, threshold bands and rounding rules. SGC has no published back table; an unconstrained result may have a null maximum_grade.
Common integration errors
- Do not confuse HTTP 200 needs-review or failed with an HTTP validation error. Display advice and offer a new photo or correction.
- 400 INVALID_PARAMETER: inspect details.param and, for service validation, details.service_code. Check face, corner order, matching card_size and geometry_version, nested/flat duplicate ratios, and confirmed_by: person. Unknown face or axis must be supplied by the person.
- 413 PAYLOAD_TOO_LARGE limits the whole request to 10 MiB, including multipart or base64 overhead. More than 40 MP or unsupported/multiframe images are service validation errors (400 INVALID_PARAMETER). 415 UNSUPPORTED_MEDIA_TYPE means the HTTP body type is wrong; confirm and grade require application/json.
- 403 PLAN_REQUIRED: verify email for the trial, check the separate 5-call counters, or choose Growth or higher. Developer is excluded. Read details.next_step when present.
- 503 PROVIDER_UNAVAILABLE has Retry-After: 2. Credits and trial reservations are refunded. 503 FEATURE_NOT_CONFIGURED means the deployment has centering disabled. Do not loop on plan or configuration refusals.