{
  "openapi": "3.1.0",
  "info": {
    "title": "Cards API",
    "version": "1.0.0",
    "summary": "Make, read and change flashcard study sets. No account and no key.",
    "description": "Cards is free flashcards and study sets by OpenApps, a free alternative to Quizlet. Anyone can make a set with no account and no key. A set's id works like its study link: anyone with it can read the set, but not change it. Sets are not listed anywhere. Making a set returns an ownerKey; send it in the x-owner-key header to change the set, and give it only to the person the set belongs to. Request bodies can be up to 128 KiB. Errors answer {\"error\": \"<sentence for a person>\"}.",
    "contact": { "name": "Cards by OpenApps", "url": "https://cards-free.openapps.xyz/" }
  },
  "externalDocs": { "description": "Guide for AI assistants", "url": "https://cards-free.openapps.xyz/llms.txt" },
  "servers": [
    { "url": "https://functions.bounded.sh/apps/2fa1af962bc28ba77ece4b04/api" }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "listRoutes",
        "summary": "Describe the API and list its routes",
        "responses": {
          "200": {
            "description": "The API's name, a short description, where the docs are, and its routes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": { "type": "string" },
                    "about": { "type": "string" },
                    "docs": { "type": "string", "format": "uri" },
                    "routes": { "type": "array", "items": { "type": "string" } }
                  },
                  "required": ["name", "about", "docs", "routes"]
                },
                "example": {
                  "name": "Cards by OpenApps",
                  "about": "Free flashcards and study sets. No key and no account to make a set. A set's id lets anyone study it, like its link; changes need the owner key returned at creation, sent as the x-owner-key header.",
                  "docs": "https://cards-free.openapps.xyz/llms.txt",
                  "routes": [
                    "POST /sets {title, description?, termLanguage?, definitionLanguage?, cards: [{term, definition}]} -> setId, url (study link), adminUrl, ownerKey, count",
                    "GET /sets/:id -> title, description, languages and every card",
                    "PATCH /sets/:id (x-owner-key) {title?, description?, termLanguage?, definitionLanguage?, cards? (replaces all), addCards? (appends)}"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/sets": {
      "post": {
        "operationId": "createSet",
        "summary": "Make a study set",
        "description": "Makes a set and returns its study link (url, to share with anyone who should study it) and its private edit link (adminUrl, for the person the set belongs to). For a set larger than one 128 KiB request, send the first cards here and add the rest with PATCH and addCards.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/NewSet" },
              "example": {
                "title": "Cell biology, week 3",
                "description": "Organelles from Tuesday's lecture",
                "termLanguage": "en",
                "definitionLanguage": "en",
                "cards": [
                  { "term": "Mitochondria", "definition": "Makes energy (ATP) for the cell" },
                  { "term": "Ribosome", "definition": "Builds proteins" }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The set was made.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CreatedSet" },
                "example": {
                  "setId": "a8KdW3mPq2ZxR7tLb9Nc",
                  "url": "https://cards-free.openapps.xyz/s/a8KdW3mPq2ZxR7tLb9Nc",
                  "adminUrl": "https://cards-free.openapps.xyz/s/a8KdW3mPq2ZxR7tLb9Nc/edit#k=Vq3LmZ8xKp2RtN7bWc4Yh9Ds",
                  "ownerKey": "Vq3LmZ8xKp2RtN7bWc4Yh9Ds",
                  "title": "Cell biology, week 3",
                  "count": 2,
                  "share": "Send url to anyone who should study the set: they can study it and make their own copy, but not change it. Give adminUrl only to the person the set belongs to: it lets them edit it on any device."
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "413": { "$ref": "#/components/responses/TooLarge" }
        }
      }
    },
    "/sets/{setId}": {
      "parameters": [
        {
          "name": "setId",
          "in": "path",
          "required": true,
          "description": "The set's id, the last part of its study link.",
          "schema": { "type": "string", "pattern": "^[A-Za-z0-9]{8,32}$" },
          "example": "a8KdW3mPq2ZxR7tLb9Nc"
        }
      ],
      "get": {
        "operationId": "getSet",
        "summary": "Read a set and every card in it",
        "responses": {
          "200": {
            "description": "The set, with its cards in order.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Set" },
                "example": {
                  "setId": "a8KdW3mPq2ZxR7tLb9Nc",
                  "url": "https://cards-free.openapps.xyz/s/a8KdW3mPq2ZxR7tLb9Nc",
                  "title": "Cell biology, week 3",
                  "description": "Organelles from Tuesday's lecture",
                  "termLanguage": "en",
                  "definitionLanguage": "en",
                  "count": 2,
                  "updatedAt": "2026-10-19T13:25:26.000Z",
                  "cards": [
                    { "term": "Mitochondria", "definition": "Makes energy (ATP) for the cell" },
                    { "term": "Ribosome", "definition": "Builds proteins" }
                  ]
                }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "410": { "$ref": "#/components/responses/Deleted" }
        }
      },
      "patch": {
        "operationId": "changeSet",
        "summary": "Change a set (owner)",
        "description": "Changes only what you send. cards replaces every card (cards whose term is unchanged keep their study progress); addCards adds cards to the end. Needs the set's owner key in the x-owner-key header.",
        "security": [{ "ownerKey": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SetChange" },
              "example": {
                "title": "Cell biology, weeks 3 and 4",
                "addCards": [{ "term": "Nucleus", "definition": "Holds the cell's DNA" }]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated set, like GET.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Set" },
                "example": {
                  "setId": "a8KdW3mPq2ZxR7tLb9Nc",
                  "url": "https://cards-free.openapps.xyz/s/a8KdW3mPq2ZxR7tLb9Nc",
                  "title": "Cell biology, weeks 3 and 4",
                  "description": "Organelles from Tuesday's lecture",
                  "termLanguage": "en",
                  "definitionLanguage": "en",
                  "count": 3,
                  "updatedAt": "2026-10-19T13:25:39.000Z",
                  "cards": [
                    { "term": "Mitochondria", "definition": "Makes energy (ATP) for the cell" },
                    { "term": "Ribosome", "definition": "Builds proteins" },
                    { "term": "Nucleus", "definition": "Holds the cell's DNA" }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": {
            "description": "No owner key was sent.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "Send the set's owner key in the x-owner-key header." } } }
          },
          "403": {
            "description": "The owner key does not match this set.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "That owner key does not match this set." } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "410": { "$ref": "#/components/responses/Deleted" },
          "413": { "$ref": "#/components/responses/TooLarge" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ownerKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-owner-key",
        "description": "The ownerKey returned when the set was made. It is a password for that one set."
      }
    },
    "schemas": {
      "Language": {
        "type": "string",
        "description": "A language code the app uses to read that side aloud, like \"es\", \"fr-FR\" or \"pt-BR\". Empty for none.",
        "pattern": "^$|^[a-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$",
        "maxLength": 16,
        "examples": ["es", "fr-FR", "pt-BR"]
      },
      "CardInput": {
        "description": "A card as {term, definition} or as a pair [term, definition]. A term is up to 1,000 characters, a definition up to 2,000. Both are required.",
        "oneOf": [
          { "$ref": "#/components/schemas/Card" },
          {
            "type": "array",
            "prefixItems": [
              { "type": "string", "minLength": 1, "maxLength": 1000 },
              { "type": "string", "minLength": 1, "maxLength": 2000 }
            ],
            "minItems": 2,
            "maxItems": 2
          }
        ],
        "examples": [{ "term": "hablar", "definition": "to speak" }, ["comer", "to eat"]]
      },
      "Card": {
        "type": "object",
        "properties": {
          "term": { "type": "string", "minLength": 1, "maxLength": 1000 },
          "definition": { "type": "string", "minLength": 1, "maxLength": 2000 }
        },
        "required": ["term", "definition"]
      },
      "NewSet": {
        "type": "object",
        "properties": {
          "title": { "type": "string", "minLength": 1, "maxLength": 120 },
          "description": { "type": "string", "maxLength": 1000 },
          "termLanguage": { "$ref": "#/components/schemas/Language" },
          "definitionLanguage": { "$ref": "#/components/schemas/Language" },
          "cards": { "type": "array", "items": { "$ref": "#/components/schemas/CardInput" }, "minItems": 1, "maxItems": 2000 }
        },
        "required": ["title", "cards"]
      },
      "SetChange": {
        "type": "object",
        "description": "Send any of these. A field you leave out keeps its value; send \"\" to clear the description or a language.",
        "properties": {
          "title": { "type": "string", "minLength": 1, "maxLength": 120 },
          "description": { "type": "string", "maxLength": 1000 },
          "termLanguage": { "$ref": "#/components/schemas/Language" },
          "definitionLanguage": { "$ref": "#/components/schemas/Language" },
          "cards": { "type": "array", "description": "Replaces every card.", "items": { "$ref": "#/components/schemas/CardInput" }, "minItems": 1, "maxItems": 2000 },
          "addCards": { "type": "array", "description": "Adds cards to the end.", "items": { "$ref": "#/components/schemas/CardInput" } }
        }
      },
      "CreatedSet": {
        "type": "object",
        "properties": {
          "setId": { "type": "string" },
          "url": { "type": "string", "format": "uri", "description": "The study link: share it with anyone who should study the set." },
          "adminUrl": { "type": "string", "format": "uri", "description": "The private edit link, for the person the set belongs to. It carries the owner key." },
          "ownerKey": { "type": "string", "description": "Send it in the x-owner-key header to change the set." },
          "title": { "type": "string" },
          "count": { "type": "integer", "description": "How many cards the set has." },
          "share": { "type": "string", "description": "Which link to give to whom, in plain words." }
        },
        "required": ["setId", "url", "adminUrl", "ownerKey", "title", "count", "share"]
      },
      "Set": {
        "type": "object",
        "properties": {
          "setId": { "type": "string" },
          "url": { "type": "string", "format": "uri", "description": "The study link." },
          "title": { "type": "string" },
          "description": { "type": "string" },
          "termLanguage": { "type": ["string", "null"] },
          "definitionLanguage": { "type": ["string", "null"] },
          "count": { "type": "integer" },
          "updatedAt": { "type": ["string", "null"], "format": "date-time" },
          "cards": { "type": "array", "items": { "$ref": "#/components/schemas/Card" } }
        },
        "required": ["setId", "url", "title", "description", "termLanguage", "definitionLanguage", "count", "updatedAt", "cards"]
      },
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string", "description": "A sentence that says what to change." } },
        "required": ["error"]
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Something in the request needs changing; the error says what.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "Card 2 needs both a term and a definition." } } }
      },
      "NotFound": {
        "description": "There is no set with that id.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "No study set with that id." } } }
      },
      "Deleted": {
        "description": "The set's owner deleted it.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "This study set was deleted by its owner." } } }
      },
      "TooLarge": {
        "description": "The request body is over 128 KiB. Send fewer cards and add the rest with PATCH and addCards.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "Public function body exceeds 131072 bytes" } } }
      }
    }
  }
}
