Card Grading

The Card Grading service uses AI to automate the grading of trading and sports cards. For each image it locates the card, identifies its corners and edges, and computes grades for individual corners, edges, overall surface, and centering — similar to PSA, Beckett, CGC, and others.

It also identifies whether the image shows the Front or Back of the card, whether the card has an Autograph, and whether it is a TCG (trading card game) or a sports card (sport).

All access is asynchronous

Card grading is exposed only through the asynchronous request API. You submit a job, the backend queues it, a worker processes it, and you either poll for the result or receive a webhook callback. There are no synchronous card-grader/v2/* endpoints — the previous synchronous endpoints have been retired.

The full claim / poll / webhook lifecycle is documented at Async API. This page covers only the card-grader-specific payload shape and responses for each endpoint.

POST  https://api.ximilar.com/account/v2/request/        # submit
GET   https://api.ximilar.com/account/v2/request/__ID__  # poll status / read result

When submitting, set "type": "card-grader" and pick one of the five values for endpoint:

endpoint valueWhat it does
gradeFull grading — centering, corners, edges, surface, final grade and condition label
conditionFast condition check only (Near Mint / Played / Damaged etc.), no per-region grades
centeringCentering analysis only (left/right + top/bottom ratios + grade)
localizeCard localization only — corners, edges, bounding box
crop_levelCropped + perspective-warped + auto-levelled card image uploaded to S3

Optional webhook field on the submit body lets you skip polling — the worker POSTs {id, status, response} to your URL after the job completes (see Async API for the webhook payload shape).


POST/account/v2/request/

Submit a card-grading request

Submit a JSON body with type: "card-grader", the desired endpoint, your image records, and (optional) webhook.

The response gives you an id. The job state moves from CREATEDPROCESSINGDONE. Poll the GET endpoint or wait for the webhook.

Required attributes

  • Name
    Authorization
    Type
    string
    Description

    API token for authentication, sent as Authorization: Token <your-token>.

  • Name
    type
    Type
    string
    Description

    Must be "card-grader".

  • Name
    endpoint
    Type
    string
    Description

    One of grade, condition, centering, localize, crop_level.

  • Name
    records
    Type
    array
    Max
    Maximum:10
    Description

    List of images to analyze. Each record must contain either _url or _base64. For endpoint: "grade" the per-request limit is 2 (front + back of one card); for the other endpoints it is 10.

Optional attributes

  • Name
    webhook
    Type
    object
    Description

    { "url": "...", "headers": { ... } }. When set, the worker POSTs the final {id, status, response} payload to this URL.

Submit request

POST
/account/v2/request/
curl --request POST \
  --url https://api.ximilar.com/account/v2/request/ \
  --header 'Authorization: Token __API_TOKEN__' \
  --header 'content-type: application/json' \
  --data '{
    "type": "card-grader",
    "endpoint": "grade",
    "records": [{ "_url": "__IMAGE_URL__" }]
  }'

Submit response

{
  "id": "2e072580-fa20-47d7-aaa3-63ae32bcec7b",
  "type": "card-grader",
  "status": "CREATED",
  "created": "2026-05-26T15:27:46.556452Z",
  "workspace": "88431559-20ea-4d43-98a6-7a1cb38160e3",
  "request": {
    "type": "card-grader",
    "endpoint": "grade",
    "records": [{ "_url": "__IMAGE_URL__" }]
  }
}

Poll for result

GET
/account/v2/request/__ID__
curl --request GET \
  --url https://api.ximilar.com/account/v2/request/__ID__ \
  --header 'Authorization: Token __API_TOKEN__'

GET response envelope when status="DONE"

{
  "id":        "61e14a37-037d-4581-9bf4-c9f001c86520",
  "type":      "card-grader",
  "status":    "DONE",
  "created":   "2026-05-27T12:51:53.649648Z",
  "modified":  "2026-05-27T12:51:58.456571Z",
  "workspace": "88431559-20ea-4d43-98a6-7a1cb38160e3",
  "request":   { "type": "card-grader", "endpoint": "grade", "records": [...] },
  "response": {
    "type": "card-grader",
    "endpoint": "grade",
    "records": [ /* one entry per submitted record — shapes for each endpoint below */ ],
    "statistics": { "time_stats": { "GradingProcessor": 8.78 } }
  }
}

Per-endpoint sections below show only the inner record that lives at response.records[i]. The outer envelope is identical for all endpoints.


endpoint: "grade" — full grading

Returns:

  • The positions of the card, its corners and edges.
  • Individual grades for each (card, corners, edges).
  • The results live in the corners, edges, and card fields.
  • An overall (averaged) grade named final inside grades, alongside the repeated per-component grades.

The _tags field on card[0] contains:

  • the card Category (e.g., Trading Card Game or Sport)
  • whether the card is Damaged
  • whether it has an Autograph
  • which Side is shown (Front or Back)

All grades range from 1 to 10, where 1 is the worst and 10 is the best. The final grade is the geometric mean of corners, edges, surface, and centering:

final = geomean(geomean(CORNERS), geomean(EDGES), SURFACE, CENTERING)

If you submit both front and back in the same records array, the final grade is a weighted average: 70 % front and 30 % back by default. Adjust with the front_weight setting (range 0.0–1.0).

Based on the final grade the response includes a condition label: Poor, Fair, Good, Very Good, Excellent, Near Mint, Mint, or Gem Mint.

Visualizations are uploaded to S3 and surfaced as:

  • _full_url_card — original image with detected objects and grades overlaid.
  • _exact_url_card — extracted upright card with centering visualization.

Returns (inside response.records[0])

  • Name
    _objects
    Type
    array
    Description

    Detected card object with bound_box and confidence.

  • Name
    _points
    Type
    array
    Description

    Four corner points in image coordinates (UL, UR, DR, DL).

  • Name
    corners
    Type
    array
    Description

    Per-corner bound_box, point, and grade.

  • Name
    edges
    Type
    array
    Description

    Per-edge polygon and grade.

  • Name
    card
    Type
    array
    Description

    Card-level info: polygon, bound_box, surface.grade, centering, _tags.

  • Name
    grades
    Type
    object
    Description

    corners, edges, surface, centering, final, condition.

  • Name
    versions
    Type
    object
    Description

    Short hashes of every model used (detection, points, corners, edges, surface, centering, final).

  • Name
    _full_url_card
    Type
    string
    Description
    URL to the annotated overview image.
  • Name
    _exact_url_card
    Type
    string
    Description
    URL to the extracted-card visualization.

Submit body

{
  "type": "card-grader",
  "endpoint": "grade",
  "records": [
    { "_url": "__IMAGE_URL__" }
  ]
}

response.records[0]

{
  "_id":     "Submission IMAGE ID",
  "_url":    "__IMAGE_URL__",
  "_status": {
    "code": 200, "text": "OK",
    "request_id": "5f6c8d38-f30f-4587-851e-3ee17f60fc1f"
  },
  "_width":  792,
  "_height": 1068,
  "_objects": [
    {
      "id":   "e0f155e9-2978-4524-bae2-3d7d3377c2c4",
      "name": "Card",
      "prob": 0.93359375,
      "bound_box": [0, 0, 792, 1068]
    }
  ],
  "_points": [
    [17.0819, 7.3722], [767.3779, 7.8225],
    [764.5919, 1057.4310], [18.8924, 1052.8937]
  ],
  "corners": [
    { "name": "UPPER_LEFT",  "bound_box": [0,   0,    69,  59],   "point": [17,  7],    "grade": 8.5 },
    { "name": "UPPER_RIGHT", "bound_box": [714, 0,    792, 59],   "point": [767, 7],    "grade": 7.5 },
    { "name": "DOWN_RIGHT",  "bound_box": [711, 1004, 791, 1068], "point": [764, 1057], "grade": 8.5 },
    { "name": "DOWN_LEFT",   "bound_box": [0,   999,  70,  1068], "point": [18,  1052], "grade": 8.5 }
  ],
  "edges": [
    { "name": "UPPER", "polygon": [[72,  -20], [711, -20],  [711, 62],   [72,  62]],   "grade": 7.5 },
    { "name": "RIGHT", "polygon": [[711, 62],  [794, 62],   [791, 1001], [708, 1001]], "grade": 7.5 },
    { "name": "DOWN",  "polygon": [[73,  996], [708, 1001], [708, 1084], [73,  1079]], "grade": 8.5 },
    { "name": "LEFT",  "polygon": [[-10, 62],  [72,  62],   [73,  996],  [-9,  996]],  "grade": 8.0 }
  ],
  "card": [
    {
      "name": "CARD",
      "polygon":   [[17, 7], [767, 7], [764, 1057], [18, 1052]],
      "bound_box": [0, 0, 792, 1068],
      "_tags": {
        "Category":  [{ "id": "44b6f95c-b3a5-4f5e-84d0-e6d441f19b3a", "name": "Card/Sport Card", "prob": 0.98710 }],
        "Damaged":   [{ "id": "8d77cbf2-96de-4e45-b518-ea09cf77483a", "name": "OK",              "prob": 0.95114 }],
        "Autograph": [{ "id": "45ef0f10-f4c5-4065-aaab-89384369b925", "name": "No",              "prob": 0.66843 }],
        "Side":      [{ "id": "3ebc6fc4-41d4-4432-99f1-5f8c50f10413", "name": "Front",           "prob": 0.94686 }]
      },
      "surface": { "grade": 6.0 },
      "centering": {
        "left/right": "46/54", "top/bottom": "54/46",
        "bound_box": [32, 45, 711, 1010],
        "pixels":  [32, 45, 39, 39],
        "offsets": [0.0437, 0.0434, 0.0522, 0.0377],
        "grade":   9.0
      }
    }
  ],
  "grades": {
    "corners": 8.0, "edges": 8.0, "surface": 6.0,
    "centering": 9.0, "final": 7.5, "condition": "Near Mint"
  },
  "versions": {
    "detection": "segmentor",
    "points":    "segmentor",
    "corners":   "7516607f_1",
    "edges":     "b949c1b8_1",
    "surface":   "7ba660de_11",
    "centering": "5bb1214a_4",
    "final":     "7516607f_1-b949c1b8_1-7ba660de_11-5bb1214a_4-segmentor-segmentor"
  },
  "_full_url_card":  "https://s3-eu-west-1.amazonaws.com/ximilar-tmp-images/card-grading/1c54a360-5463-40ab-bf1d-5388e408e791.webp",
  "_exact_url_card": "https://s3-eu-west-1.amazonaws.com/ximilar-tmp-images/card-grading/c026da43-81d1-4fb8-a75b-13f3b17c9d2d.webp"
}

endpoint: "condition" — fast condition check

Returns the position of the card (as a bounding box) and, for the largest detected card, a Condition label. You choose the naming mode:

  • ebay (default):
    • Conditions for TCG: Near Mint, Lightly Played, Moderately Played, Damaged
    • Conditions for Sport: Near Mint, Excellent, Very Good, Poor
  • tcgplayer:
    • Conditions: Near Mint, Lightly Played, Moderately Played, Heavily Played, Damaged
  • ximilar:
    • Conditions: Gem Mint, Mint, Near Mint, Excellent, Very Good, Good, Fair, Poor
  • cardmarket:
    • Conditions: Mint, Near Mint, Excellent, Good, Light Played, Played, Poor

Optional attributes (on the submit body root)

  • Name
    mode
    Type
    string
    Default
    Default:ebay
    Description

    Default is ebay. Set to tcgplayer, ximilar, or cardmarket to change the verbal grade table.

Submit body

{
  "type": "card-grader",
  "endpoint": "condition",
  "mode": "ebay",
  "records": [
    { "_id": "Submission IMAGE ID", "_url": "__IMAGE_URL__" }
  ]
}

response.records[0]

{
  "_id":     "Submission IMAGE ID",
  "_url":    "__IMAGE_URL__",
  "_status": {
    "code": 200, "text": "OK",
    "request_id": "09859b9f-243f-4225-a031-1a5cf6b2fd14"
  },
  "_width":  792,
  "_height": 1068,
  "_objects": [
    {
      "id":    "UNKNOWN",
      "name":  "Card",
      "prob":  1.0,
      "area":  1.0,
      "bound_box": [0, 0, 792, 1068],
      "Top Category": [
        { "id": "54c8ba01-545f-409d-beb9-d8dc2fecbb26", "name": "Card", "prob": 1.0 }
      ],
      "Category": [
        { "id": "44b6f95c-b3a5-4f5e-84d0-e6d441f19b3a", "name": "Card/Sport Card",        "prob": 0.98951 },
        { "id": "089fa0cd-a399-4c6c-8420-d52a55c0ff02", "name": "Card/Trading Card Game", "prob": 0.01049 }
      ],
      "Condition": [
        {
          "id":    "ad4f0eab-a513-4218-8a3c-dcc53a4223a6",
          "name":  "Card Condition Exact",
          "value": 6.01532,
          "label": "Excellent",
          "mode":  "ebay",
          "scale": ["Poor", "Very Good", "Excellent", "Near Mint"],
          "scale_value":     3,
          "max_scale_value": 4
        }
      ]
    }
  ]
}

Notes on the _objects[0] payload

  • Name
    prob
    Type
    number
    Description

    Detection confidence for the card. The condition endpoint returns 1.0 and bound_box covering the full image when no separate detection step is run (the Flow pipeline classifies the whole image directly).

  • Name
    Category
    Type
    array
    Description

    Sorted candidates with name and prob. The condition output is computed against the top entry (here Card/Sport Card). For mode="ebay" this picks ebay-sport vs ebay-tcg automatically.

  • Name
    Condition
    Type
    array
    Description

    Single entry. value is the raw 1–10 grade; label is the verbal grade in the chosen mode; scale is the ordered list of labels for that mode; scale_value and max_scale_value let you render a position-on-scale UI without re-looking-up the table.

  • Name
    Top Category
    Type
    array
    Description

    Always [{name: "Card", ...}] — present for compatibility with downstream tools that branch on broader taxonomy nodes.


endpoint: "centering" — centering analysis

Returns the position of the card and a centering analysis (left/right + top/bottom ratios with an overall grade). Visualizations are uploaded to S3 and surfaced as _clean_url_card and _exact_url_card. The _tags field provides the card Category, Autograph and Side.

Optional per-record fields

  • Name
    transparent
    Type
    boolean
    Description

    If the image already has a card on a transparent (alpha) background, set to true to skip the heavy localization step.

  • Name
    skip-location
    Type
    boolean
    Description

    If the image is a clean scan with a perfectly aligned card, set to true to treat the whole image as the card.

  • Name
    no-image-output
    Type
    boolean
    Description

    If true, the visualization images are not generated — faster.

Submit body

{
  "type": "card-grader",
  "endpoint": "centering",
  "records": [
    { "_url": "__IMAGE_URL__" }
  ]
}

response.records[0]

{
  "_id":     "Submission IMAGE ID",
  "_url":    "__IMAGE_URL__",
  "_status": {
    "code": 200, "text": "OK",
    "request_id": "d7ebc1ce-bde6-4f39-9b70-024085329809"
  },
  "_width":  792,
  "_height": 1068,
  "_objects": [
    {
      "id":   "e0f155e9-2978-4524-bae2-3d7d3377c2c4",
      "name": "Card",
      "prob": 0.93359375,
      "bound_box": [0, 0, 792, 1068]
    }
  ],
  "_points": [
    [17.0819, 7.3722], [767.3779, 7.8225],
    [764.5919, 1057.4310], [18.8924, 1052.8937]
  ],
  "card": [
    {
      "name": "CARD",
      "polygon":   [[17, 7], [767, 7], [764, 1057], [18, 1052]],
      "bound_box": [0, 0, 792, 1068],
      "_tags": {
        "Category":  [{ "id": "44b6f95c-b3a5-4f5e-84d0-e6d441f19b3a", "name": "Card/Sport Card", "prob": 0.98710 }],
        "Autograph": [{ "id": "45ef0f10-f4c5-4065-aaab-89384369b925", "name": "No",              "prob": 0.66843 }],
        "Side":      [{ "id": "3ebc6fc4-41d4-4432-99f1-5f8c50f10413", "name": "Front",           "prob": 0.94686 }]
      },
      "centering": {
        "left/right": "46/54", "top/bottom": "54/46",
        "bound_box": [32, 45, 711, 1010],
        "pixels":  [32, 45, 39, 39],
        "offsets": [0.0437, 0.0434, 0.0522, 0.0377],
        "grade": 9.0
      }
    }
  ],
  "grades":   { "centering": 9.0 },
  "versions": { "detection": "segmentor", "points": "segmentor", "centering": "5bb1214a_4" },
  "_clean_url_card": "https://s3-eu-west-1.amazonaws.com/ximilar-tmp-images/card-grading/2ada3b73-7717-4c2e-a408-5b7bc3eee2fa.webp",
  "_exact_url_card": "https://s3-eu-west-1.amazonaws.com/ximilar-tmp-images/card-grading/fbb8bf8e-788e-4e60-aca3-150ca1f7835e.webp"
}

Notes on the centering payload

  • Name
    _points
    Type
    array
    Description

    Four corner points (UL, UR, DR, DL) returned by the segmentor in original-image coordinates as floats. card[0].polygon is the integer-rounded version of the same points.

  • Name
    card[0].centering
    Type
    object
    Description

    left/right and top/bottom are human-readable percentage strings. pixels is the per-side pixel margin [left, top, right, bottom]. offsets is each margin as a fraction of the card's relevant dimension. bound_box is the centered inner-frame box in coordinates relative to the warped card crop.

  • Name
    _clean_url_card
    Type
    string
    Description

    S3 URL of the warped upright card (no overlay).

  • Name
    _exact_url_card
    Type
    string
    Description

    S3 URL of the warped card with centering visualization drawn on top.

  • Name
    versions
    Type
    object
    Description

    Short hashes of the models used (detection and points both come from the segmentor service; centering from the centering model).


endpoint: "localize" — card location only

Returns the positions of corners, edges, and the overall card in the image. No grades — fastest of the grading endpoints.

Optional per-record fields

  • Name
    transparent
    Type
    boolean
    Description

    If the image already has a card on a transparent (alpha) background, set to true to skip the heavy localization step.

Submit body

{
  "type": "card-grader",
  "endpoint": "localize",
  "records": [
    { "_url": "__IMAGE_URL__" }
  ]
}

response.records[0]

{
  "_id":     "Submission IMAGE ID",
  "_url":    "__IMAGE_URL__",
  "_status": {
    "code": 200, "text": "OK",
    "request_id": "b757ea33-7a78-4907-8d20-02b9f834affd"
  },
  "_width":  792,
  "_height": 1068,
  "_objects": [
    {
      "id":   "e0f155e9-2978-4524-bae2-3d7d3377c2c4",
      "name": "Card",
      "prob": 0.93359375,
      "bound_box": [0, 0, 792, 1068]
    }
  ],
  "_points": [
    [17.0819, 7.3722], [767.3779, 7.8225],
    [764.5919, 1057.4310], [18.8924, 1052.8937]
  ],
  "corners": [
    { "name": "UPPER_LEFT",  "bound_box": [0,   0,    69,  59],   "point": [17,  7]    },
    { "name": "UPPER_RIGHT", "bound_box": [714, 0,    792, 59],   "point": [767, 7]    },
    { "name": "DOWN_RIGHT",  "bound_box": [711, 1004, 791, 1068], "point": [764, 1057] },
    { "name": "DOWN_LEFT",   "bound_box": [0,   999,  70,  1068], "point": [18,  1052] }
  ],
  "edges": [
    { "name": "UPPER", "polygon": [[72,  -20], [711, -20],  [711, 62],   [72,  62]]   },
    { "name": "RIGHT", "polygon": [[711, 62],  [794, 62],   [791, 1001], [708, 1001]] },
    { "name": "DOWN",  "polygon": [[73,  996], [708, 1001], [708, 1084], [73,  1079]] },
    { "name": "LEFT",  "polygon": [[-10, 62],  [72,  62],   [73,  996],  [-9,  996]]  }
  ],
  "card": [
    {
      "name": "CARD",
      "polygon":   [[17, 7], [767, 7], [764, 1057], [18, 1052]],
      "bound_box": [0, 0, 792, 1068]
    }
  ],
  "versions": { "detection": "segmentor", "points": "segmentor" }
}

endpoint: "crop_level" — cropped and levelled card image

Given a card image, returns a URL to a new image that is cropped + perspective-warped + auto-levelled + sharpened. Useful as a normalised input for downstream tools or display.

Optional per-record fields

  • Name
    level
    Type
    boolean
    Default
    Default:true
    Description

    Defaults to true. Set to false to skip the histogram-clip + sharpen step and upload the raw warped card. Useful when you want to apply your own color/levelling pipeline.

Submit body

{
  "type": "card-grader",
  "endpoint": "crop_level",
  "records": [
    { "_url": "__IMAGE_URL__" },
    { "_url": "__IMAGE_URL_2__", "level": false }
  ]
}

response.records[0]

{
  "_id":     "Submission IMAGE ID",
  "_url":    "__IMAGE_URL__",
  "_status": {
    "code": 200, "text": "OK",
    "request_id": "2195f836-df4f-4735-8406-b282e6481dc0"
  },
  "_width":  792,
  "_height": 1068,
  "_img_url": "https://s3-eu-west-1.amazonaws.com/ximilar-tmp-images/card_crop_level/9bfc46c3-701d-4282-8bd5-6df22701b345.webp",
  "versions": { "detection": "segmentor", "points": "segmentor" }
}
  • Name
    _img_url
    Type
    string
    Description

    S3 URL of the cropped, perspective-warped (and, by default, auto-levelled + sharpened) card. The image is uploaded as .webp to the ximilar-tmp-images/card_crop_level/ prefix; URLs are publicly readable.

  • Name
    versions
    Type
    object
    Description

    Short hashes / source labels of the localization step. For the crop endpoint both detection and points come from the segmentor service.

Was this page helpful?