{"openapi":"3.1.0","info":{"title":"Flashcards.gg API","version":"1.0.0","description":"REST API for managing flashcard sets at flashcards.gg. Auth via `Authorization: Bearer flc_<key>`. Get a key at flashcards.gg/account/api-keys. See /developers for the full guide.","contact":{"name":"Flashcards.gg","url":"https://flashcards.gg","email":"flashcards.gg@gmail.com"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://flashcards.gg","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"sets","description":"Flashcard sets — CRUD over your decks."},{"name":"cards","description":"Single-card operations within a set."},{"name":"shares","description":"Public share links (snapshot semantics)."}],"paths":{"/api/v1/sets":{"get":{"operationId":"listSets","summary":"List my flashcard sets","description":"Paginated list of sets I own, newest first. Use `?name=X` to resolve a name to an id. Cursor-paginated; pass back the `cursor` from the prior response. Response uses a summary shape (cardCount, no cards) — GET /sets/{id} for cards.","tags":["sets"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size (1-100, default 50).","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous response.","schema":{"type":"string"}},{"name":"name","in":"query","required":false,"description":"Exact-match filter by set name (case sensitive).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"createSet","summary":"Create a new flashcard set","description":"Create a set with optional initial cards. Returns the full set with assigned id and ETag header. 409 if you already own a set with the same name.","tags":["sets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCreate"}}}},"responses":{"201":{"description":"Created","headers":{"ETag":{"schema":{"type":"string"}},"Location":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Set"}}}},"400":{"$ref":"#/components/responses/Validation"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/NameTaken"}}}},"/api/v1/sets/{id}":{"get":{"operationId":"getSet","summary":"Get a single set with its cards","description":"Returns the full set including all cards. ETag header included for optimistic locking.","tags":["sets"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}}],"responses":{"200":{"description":"OK","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Set"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"replaceSet","summary":"Replace the entire set","description":"Replaces name, cards, and metadata. Anything you omit resets to default (null). Use PATCH to update only some fields.","tags":["sets"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCreate"}}}},"responses":{"200":{"description":"OK","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Set"}}}},"400":{"$ref":"#/components/responses/Validation"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/NameTaken"},"412":{"$ref":"#/components/responses/StaleEtag"}}},"patch":{"operationId":"updateSet","summary":"Partially update a set","description":"Updates only the fields you pass. Common uses: rename, change study direction, swap card list.","tags":["sets"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPatch"}}}},"responses":{"200":{"description":"OK","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Set"}}}},"400":{"$ref":"#/components/responses/Validation"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/NameTaken"},"412":{"$ref":"#/components/responses/StaleEtag"}}},"delete":{"operationId":"deleteSet","summary":"Delete a set permanently","description":"Removes the set and all its cards. Sharing links to the set become 404.","tags":["sets"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}}],"responses":{"204":{"description":"Deleted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"412":{"$ref":"#/components/responses/StaleEtag"}}}},"/api/v1/sets/{id}/cards":{"post":{"operationId":"addCard","summary":"Add a single card to a set","description":"Appends one card. Use this instead of PUT/PATCH for atomic \"add one card\" — standard LLM pattern. The card id is server-generated and returned so you can later PATCH or DELETE it.","tags":["cards"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardCreate"}}}},"responses":{"201":{"description":"Created","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Card"}}}},"400":{"$ref":"#/components/responses/Validation"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/sets/{id}/cards/batch":{"post":{"operationId":"addCardsBatch","summary":"Add many cards to a set in one call","description":"Atomically appends up to 500 cards. Use this for bulk imports when single addCard calls would hit the 100/min rate limit, or when sending the full set via replaceSet would bloat the request. Returns each card with its assigned id.","tags":["cards"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardBatchCreate"}}}},"responses":{"201":{"description":"Created","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardBatchResult"}}}},"400":{"$ref":"#/components/responses/Validation"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/sets/{id}/cards/{cardId}":{"patch":{"operationId":"updateCard","summary":"Update a single card","description":"Updates `key`, `value`, or both on a single card.","tags":["cards"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}},{"name":"cardId","in":"path","required":true,"description":"Card id, format card_<…>.","schema":{"type":"string","pattern":"^card_.+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CardPatch"}}}},"responses":{"200":{"description":"OK","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Card"}}}},"400":{"$ref":"#/components/responses/Validation"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"set_not_found or card_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteCard","summary":"Delete a single card from a set","description":"Removes one card from the set. 404 if the set or card is gone.","tags":["cards"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}},{"name":"cardId","in":"path","required":true,"description":"Card id, format card_<…>.","schema":{"type":"string","pattern":"^card_.+$"}}],"responses":{"204":{"description":"Deleted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"set_not_found or card_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/sets/{id}/shares":{"post":{"operationId":"createShare","summary":"Create a public share link (snapshot)","description":"Snapshots the set as-is and returns a public URL. Later edits to the source set do NOT update the snapshot. Multiple snapshots can co-exist; manage via list/revoke.","tags":["shares"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}}],"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareCreated"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"get":{"operationId":"listShares","summary":"List active share links for a set","description":"Share links you created for this set, newest first. Legacy shares from the Flutter app are not listed (no source link).","tags":["shares"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/sets/{id}/shares/{token}":{"delete":{"operationId":"revokeShare","summary":"Revoke a share link","description":"Removes the snapshot. Public reads of the token will return 404 immediately.","tags":["shares"],"parameters":[{"name":"id","in":"path","required":true,"description":"Set id, format set_<24hex>.","schema":{"type":"string","pattern":"^set_[0-9a-f]{24}$"}},{"name":"token","in":"path","required":true,"description":"16-char share token.","schema":{"type":"string"}}],"responses":{"204":{"description":"Revoked"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Share not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/shared/{token}":{"get":{"operationId":"readShared","summary":"Public read of a shared set (no auth)","description":"Resolves a share token to its snapshot. No auth required. Use to fetch a friend's shared deck — then POST /sets to import it as your own.","tags":["shares"],"security":[],"parameters":[{"name":"token","in":"path","required":true,"description":"16-char share token.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"ETag":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SharedSet"}}}},"404":{"description":"Share not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"flc_<32hex>","description":"Personal API key from /account/api-keys."}},"responses":{"Unauthorized":{"description":"auth_required — missing or invalid Authorization header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"Malformed id, bad parameter shape, or similar","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Validation":{"description":"Field validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"set_not_found — set does not exist or you do not own it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NameTaken":{"description":"name_taken — you already have a set with this name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"StaleEtag":{"description":"stale_etag — set was modified since your fetch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaleEtagError"}}}},"RateLimited":{"description":"rate_limited — 100 req/min cap exceeded; Retry-After header set","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitedError"}}}}},"schemas":{"Set":{"type":"object","required":["id","name","cards","updatedAt"],"properties":{"id":{"type":"string","pattern":"^set_[0-9a-f]{24}$"},"name":{"type":"string","maxLength":200},"cards":{"type":"array","items":{"$ref":"#/components/schemas/Card"},"maxItems":10000},"updatedAt":{"type":"string","format":"date-time"},"frontLang":{"type":["string","null"],"description":"BCP-47 language tag (e.g. \"es\", \"zh-Hant\")."},"backLang":{"type":["string","null"]},"studySide":{"type":["string","null"],"enum":["front","back",null],"description":"Which side shows first in study modes."}}},"SetSummary":{"type":"object","required":["id","name","cardCount","updatedAt"],"properties":{"id":{"type":"string","pattern":"^set_[0-9a-f]{24}$"},"name":{"type":"string"},"cardCount":{"type":"integer","minimum":0},"updatedAt":{"type":"string","format":"date-time"},"frontLang":{"type":["string","null"]},"backLang":{"type":["string","null"]},"studySide":{"type":["string","null"],"enum":["front","back",null]}}},"SetList":{"type":"object","required":["data","cursor","hasMore"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SetSummary"}},"cursor":{"type":["string","null"],"description":"Pass back as ?cursor= for the next page. Null when no more."},"hasMore":{"type":"boolean"}}},"SetCreate":{"type":"object","required":["name","cards"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"cards":{"type":"array","items":{"$ref":"#/components/schemas/CardCreate"},"maxItems":10000},"frontLang":{"type":["string","null"],"maxLength":20},"backLang":{"type":["string","null"],"maxLength":20},"studySide":{"type":["string","null"],"enum":["front","back",null]}}},"SetPatch":{"type":"object","description":"Any subset of these fields. Server preserves anything you omit.","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"cards":{"type":"array","items":{"$ref":"#/components/schemas/CardCreate"},"maxItems":10000},"frontLang":{"type":["string","null"],"maxLength":20},"backLang":{"type":["string","null"],"maxLength":20},"studySide":{"type":["string","null"],"enum":["front","back",null]}}},"Card":{"type":"object","required":["id","key","value"],"properties":{"id":{"type":"string","pattern":"^card_.+$"},"key":{"type":"string","description":"Front side of the card.","maxLength":2000},"value":{"type":"string","description":"Back side of the card.","maxLength":2000}}},"CardCreate":{"type":"object","required":["key","value"],"properties":{"key":{"type":"string","maxLength":2000},"value":{"type":"string","maxLength":2000}}},"CardBatchCreate":{"type":"object","required":["cards"],"properties":{"cards":{"type":"array","minItems":1,"maxItems":500,"description":"Cards to append. Each gets a server-generated id.","items":{"$ref":"#/components/schemas/CardCreate"}}}},"CardBatchResult":{"type":"object","required":["added","cards"],"properties":{"added":{"type":"integer","description":"Count of cards appended."},"cards":{"type":"array","description":"Cards as stored, with their assigned ids, in input order.","items":{"$ref":"#/components/schemas/Card"}}}},"CardPatch":{"type":"object","properties":{"key":{"type":"string","maxLength":2000},"value":{"type":"string","maxLength":2000}}},"ShareCreated":{"type":"object","required":["token","url","createdAt"],"properties":{"token":{"type":"string","description":"16-char alphanumeric token."},"url":{"type":"string","description":"Public URL — flashcards.gg/s/<token>."},"createdAt":{"type":"string","format":"date-time"}}},"ShareList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ShareCreated"}}}},"SharedSet":{"type":"object","required":["token","name","cards","createdAt"],"properties":{"token":{"type":"string"},"name":{"type":"string"},"cards":{"type":"array","items":{"$ref":"#/components/schemas/Card"}},"createdAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"Stable machine-readable code, e.g. \"set_not_found\"."},"message":{"type":"string","description":"Human-readable."},"field":{"type":"string","description":"For validation errors, the field name."}}},"StaleEtagError":{"type":"object","required":["error","message","currentEtag"],"properties":{"error":{"type":"string","enum":["stale_etag"]},"message":{"type":"string"},"currentEtag":{"type":"string"}}},"RateLimitedError":{"type":"object","required":["error","message","retryAfterSeconds"],"properties":{"error":{"type":"string","enum":["rate_limited"]},"message":{"type":"string"},"retryAfterSeconds":{"type":"integer","minimum":1,"maximum":60}}}}}}