This is a s2cgi-based set of CGI scripts for managing card data for the Cide Site.

This API is 100% JSON. The JSON structure for the cards, along with various other project-related data, can be found over in the public GDrive folder.

Programming Language

s2, using the s2cgi CGI framework.


This service uses s2's sqlite3 loadable module to for storage. Here's the db schema.

Error Reporting

The backend always tries to respond with a valid JSON response, even when responding with HTTP codes 400 or higher (errors). If a response object has an exception property, that exception will have a message property which (hopefully) describes the problem. The message will normally be a string, but the framework allows it to be of any type, so it "might not" be a string (but usually will be). The backend normally "scrubs" exceptions to remove an stack trace/file/line info, as that's security-relevant, but that feature can be disabled when running it on a local web server, so that it sends complete exception objects, in all their glory.

If a response ever arrives without a JSON body, it's because some lower level of the infrastructure failed, e.g. bootstrapping of the CGI process, or even-lower-level code failed in a fatal way (e.g. a failed C-level or s2-level assertion). It is not expected that this actually happen in practice, but... yeah.

Loading/saving Cards



To save a card or deck, simply POST a JSON-format object using the schema described... somewhere... else... to the /card resp. /deck route. (This API does not use the PUT HTTP method because CGI does not define that method. "Pure REST" APIs which use PUT cannot be portably implemented as CGIs.) Both routes accept a single object or an array of such objects, and return the saved (and modified) object(s) in the same form.

If saving fails for any reason, e.g., due to a constraint violation or missing/invalid required property, an exception is thrown.

These requests modify the card object to set its last-updated time and, if needed. Because they are modified, the updated object(s) is/are returned by these methods. Note that saving carefully prunes any unknown properties, so the returned object will have any "extra" properties which arrived in the request stripped from it.


Currently there is almost none. Anyone can save any cards. This approach has worked relatively well for me in past niche-market projects like this one. If it gets abused, it will either be turned off or some form of authentication will be added.

This API has login support but currently only distinguishes between admin and anonymous users. An admin user is permitted to do a few things which normal users are not:

There are no high-level tools for setting up an admin user - it has to be done directly in the database using voodoo nobody on earth understands.

Log in: POST /admin/login a request payload of {"user": "name", "password": "..."}. The response, on success, will be an object with {"name":"username", "cideToken":"..."}. That cideToken part gets set as a cookie and is used to authenticate further admin-level requests.

Who Am I?: GET to /admin/whoami returns the same structure as /admin/login. If a valid auth token cookie is found, that user's login token and name are returned, otherwise an object with null values is returned. This allows an app to determine whether its auth cookie is still valid, effectively performing a continuation of an older session.

Logout: POST or GET to /admin/logout. If the auth cookie is set (or passed as a GET parameter or in the body of a POST'd JSON object), the current login will be invalidated. Regardless of success or failure (no auth token found), the same result as whoami is returned.

JSON Structures

Card JSON Structure

  "header": {
    "name": "Propane Tanks",
    //"fontSizePercent": 300, // OPTIONAL for the header (kinda).
    "deck": string, // deck name abbreviation (not its UID)
    "number": integer, // card number within that deck
    "version": integer // version number of the card
  "body": {
      "fontSizePercent": 200, // described in the GDoc docs. REQUIRED for the body
      "text": [
        // string or array of strings (which get concatenated together).
        // May contain certain specialized markup (documented elsewhere) but not HTML.
        "When killed, roll 1d6: ",
        "on {{dice 1-3}} (killed by melee weapon) or ",
        "{{dice 1-5}} (ranged weapon or car) ",
        "this zombie explodes with the effect of a Molotov. ",
        "Attacker gains no XP from explosion kills."
  "meta": { // optional "not-on-the-card" data
    "uid": string, // stable unique identifier, distinct across all decks
    "mtime": integer, // Unix Epoch timestamp of last database modification time
    "contributor": string, // optional name/URL of the card's contributor
    "locked": boolean // true if this card is "locked" (uneditable) in the db.
                  // locked has no effect when set/changed from the client except (someday) in admin mode

Deck JSON Structure

  "name": string, // Unique name. e.g. "Zombie Abilities".
  "abbr": string, // card ID/name abbreviation. e.g. "ZA".
                  // This is the key the related cards use to refer to the deck.
  "cards": array, // list of card objects. Not all HTTP requests return these.
  "meta": { // system-level state
    "uid": string, // stable unique identifier
    "mtime": integer, // Unix Epoch timestamp of last database modification time
    "locked": boolean // true if this deck is "locked" (uneditable) in the db.
            // The exact semantics of locked decks are still under consideration.