{"openapi":"3.1.0","info":{"title":"Snap That Piece API (preview)","version":"0.1.0","summary":"Read a chess position from a photo of a real board.","description":"Send a photo of a physical chessboard and get the position back as FEN, with confidence, the squares the model doubts, the board's orientation and a rendered board. Scans run asynchronously: post the photo, then poll the scan until its status is completed or failed.\n\nThis is a free preview: 5 scans a day per account, and it may change without notice. Results may be used to answer the person who asked. They may not be used to train models, build a dataset or be resold. Photos sent through the free API may be used to train our models.","termsOfService":"https://snapthatpiece.com/terms/"},"externalDocs":{"url":"https://snapthatpiece.com/developers/"},"servers":[{"url":"https://app.snapthatpiece.com"}],"security":[{"apiKey":[]}],"paths":{"/api/v1/scans":{"get":{"operationId":"listScans","summary":"List the scans sent with a key, newest first","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"before","in":"query","description":"Only scans older than this: the last page's next_before.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"A page of scans.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanList"}}}},"400":{"description":"Bad limit or before.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests from this address. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createScan","summary":"Scan a photo of a chessboard","description":"The body is the image itself, up to 10 MB. A board that is read counts against the daily limit. A photo the worker rejects does not.","requestBody":{"required":true,"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/heic":{"schema":{"type":"string","format":"binary"}},"image/heif":{"schema":{"type":"string","format":"binary"}}}},"responses":{"202":{"description":"Queued. Poll the scan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Larger than 10 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Not a JPEG, PNG, WebP or HEIC image.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over a limit. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The photo couldn't be stored. Try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The preview is at capacity today.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/scans/{scan_id}":{"get":{"operationId":"getScan","summary":"Get a scan and its result","description":"Scans read in a few seconds. Poll about once a second.","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The scan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan for this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests from this address. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/scans/{scan_id}/board.svg":{"get":{"operationId":"getScanBoard","summary":"Render the read board as an SVG","description":"Squares the model doubts carry an amber dot. On the free plan, a strip under the board carries our logo and name, so the image is a little taller than wide.","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"view","in":"query","description":"The side to draw from. Defaults to the scan's orientation.view.","schema":{"type":"string","enum":["white","black"]}}],"responses":{"200":{"description":"The board.","content":{"image/svg+xml":{"schema":{"type":"string"}}}},"400":{"description":"Unknown view.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan for this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The scan has no board to draw yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests from this address. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/scans/{scan_id}/board.png":{"get":{"operationId":"getScanBoardPng","summary":"Render the read board as a PNG","description":"Squares the model doubts carry an amber dot. On the free plan, a strip under the board carries our logo and name, so the image is a little taller than wide. Limited to 30 a minute per account.","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"view","in":"query","description":"The side to draw from. Defaults to the scan's orientation.view.","schema":{"type":"string","enum":["white","black"]}},{"name":"size","in":"query","description":"The image's width in pixels.","schema":{"type":"integer","enum":[256,512,1024],"default":512}}],"responses":{"200":{"description":"The board.","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Unknown view or size.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan for this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The scan has no board to draw yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/status":{"get":{"operationId":"getStatus","summary":"Whether scans are being read right now","description":"Public, and refreshed every 30 seconds.","security":[],"responses":{"200":{"description":"The status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}}}}}},"/api/agent/auth/register":{"post":{"operationId":"registerAgent","summary":"Ask a person for their API key","description":"Emails the person if the address is a verified account's, and answers the same either way. Show them the user_code. They approve or deny it in Settings. Then poll the claim. See https://snapthatpiece.com/auth.md.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["agent_name","email"],"properties":{"agent_name":{"type":"string","pattern":"^[A-Za-z0-9 _-]{1,40}$","description":"What to call you in the email to the person."},"email":{"type":"string","format":"email"}}}}}},"responses":{"202":{"description":"Recorded. Poll the claim.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentClaimCreated"}}}},"400":{"description":"Bad agent_name or email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/agent/auth/claims/{claim_id}":{"get":{"operationId":"getAgentClaim","summary":"See whether the person approved, and collect the key","description":"The key comes back once, in the first answer after approval, and is never shown again. Poll every few seconds. A request expires 30 minutes after it's made.","security":[],"parameters":[{"name":"claim_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Where the request stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentClaim"}}}},"404":{"description":"No such request. It may have expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/usage":{"get":{"operationId":"getUsage","summary":"How much of today's limit is left","responses":{"200":{"description":"This account's usage in the last 24 hours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"}}}},"401":{"description":"Missing or invalid key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests from this address. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Create a key in Settings at https://app.snapthatpiece.com/settings."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"A stable code to branch on.","enum":["invalid_key","rate_limited","daily_limit","too_many_attempts","unavailable","too_large","not_an_image","client_disconnected","upload_failed","not_found","not_ready","bad_view","bad_size","bad_limit","bad_before","bad_request","bad_agent_name","bad_email"]},"message":{"type":"string"}}},"Scan":{"type":"object","required":["id","status","links"],"description":"Result fields are null until status is completed.","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["pending","processing","completed","failed"]},"created_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"fen":{"type":["string","null"],"description":"The position, as its owner last confirmed it, in full FEN. A photo doesn't show whose turn it is, so treat the side to move, castling and en passant as placeholders."},"confidence":{"type":["object","null"],"description":"Classifier probabilities from 0 to 1. Not an accuracy figure.","properties":{"board":{"type":["number","null"],"description":"Mean over the occupied squares."},"mean":{"type":["number","null"],"description":"Mean over all 64 squares."}}},"unsure_squares":{"type":["array","null"],"items":{"type":"string"},"description":"Squares read below the model's usual confidence, such as e4. Check these first, but errors can be elsewhere too."},"unsure":{"type":["boolean","null"],"description":"True with two or more unsure squares: have someone check the read."},"orientation":{"type":["object","null"],"properties":{"view":{"type":"string","enum":["white","black"],"description":"The side to show the board from: the side the photo was taken from, unless its owner changed it in the app."},"ambiguous":{"type":"boolean","description":"The model couldn't tell which side is which, so the FEN may be turned around."}}},"piece_count":{"type":["integer","null"]},"board_px_per_square":{"type":["number","null"],"description":"The board's size in the photo. Under about 40, expect more errors."},"error":{"type":["string","null"],"description":"Why a failed scan failed: a stable code to branch on.","enum":["no_board","not_readable","timeout","engine_unavailable","processing_error",null]},"message":{"type":["string","null"],"description":"The same, in words to show a person."},"links":{"type":"object","properties":{"self":{"type":"string"},"board_svg":{"type":"string"},"board_png":{"type":"string"}}}}},"Status":{"type":"object","required":["status","checked_at"],"properties":{"status":{"type":"string","enum":["ok","degraded","down"],"description":"down: no worker is reading photos. degraded: scans are slow or failing on our side."},"queue_wait_s":{"type":["number","null"],"description":"Median wait before a recent scan was picked up."},"checked_at":{"type":"string","format":"date-time"}}},"AgentClaimCreated":{"type":"object","required":["claim_id","status","user_code","expires_at","poll"],"properties":{"claim_id":{"type":"string"},"status":{"type":"string","enum":["pending"]},"user_code":{"type":"string","description":"Show this to the person: Settings shows it next to the request."},"expires_at":{"type":"string","format":"date-time"},"poll":{"type":"string"},"next":{"type":"string","description":"What to tell the person."}}},"AgentClaim":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["pending","approved","denied","expired","delivered"],"description":"delivered: the key was already collected."},"key":{"type":"string","description":"The API key, only in the first answer after approval."}}},"ScanList":{"type":"object","required":["scans","next_before"],"properties":{"scans":{"type":"array","items":{"$ref":"#/components/schemas/Scan"}},"next_before":{"type":["string","null"],"format":"date-time","description":"Pass as before for the next page. Null on the last one."}}},"Usage":{"type":"object","required":["plan","daily_limit","used","remaining"],"properties":{"plan":{"type":"string","enum":["free","paid"]},"daily_limit":{"type":"integer"},"used":{"type":"integer"},"remaining":{"type":"integer"},"resets_at":{"type":["string","null"],"format":"date-time","description":"When a scan comes back, if none are left."},"attempts_used":{"type":"integer","description":"Photos sent in the last 24 hours, read or not."},"attempts_limit":{"type":"integer"}}}}}}