TextShelf

Developers › File formats Beta

Review inbox

Format textshelf-review-inbox, version 1. Written by the web, read by the Mac app. Status: v1, beta. It keeps to the rules every format follows.

A review inbox carries the reviews a web shelf's reviewers have written, so that someone reading on a Mac can see them beside their own. It goes one way: the Mac app shows the imported reviews read-only and never sends them back.

An organizer downloads it from a shelf's Export menu ("Review inbox (.json)", /submissions/export/<shelf id>/review-inbox/). By default it holds the reviews marked complete; ?include_incomplete=true adds the rest, and ?in_contention=true or false narrows it as the CSV export does. "Download everything" puts the same document in its zip as reviews.json.

The file

{
  "formatVersion": 1,
  "kind": "textshelf-review-inbox",
  "source": {
    "host": "textshelf.com",
    "contestSlug": "spring-2026-playwriting",
    "contestName": "Spring 2026 Playwriting Prize",
    "exportedAt": "2026-08-03T17:04:00.000Z"
  },
  "rubric": [
    { "slug": "innovation", "name": "Innovation", "maxScore": 5, "weight": 1.5 },
    { "slug": "craft", "name": "Craft", "maxScore": 5, "weight": 1.0 }
  ],
  "reviews": [
    {
      "externalReviewId": "django:1234",
      "submissionRef": {
        "nativeId": null,
        "externalId": "17",
        "sourcePk": 91,
        "title": "The Lighthouse Keeper's Daughter"
      },
      "reviewerName": "Reviewer 2",
      "anonymized": true,
      "submittedAt": "2026-08-01T09:30:00.000Z",
      "scores": { "innovation": 4, "craft": 3 },
      "scoreComments": { "innovation": "Fresh premise." },
      "assessment": 4,
      "moveForward": 2,
      "recommendation": "accept",
      "confidence": 3,
      "comment": "Strong opening; pacing flags in the middle third.",
      "hasCoi": false,
      "coiDescription": null,
      "complete": true
    }
  ]
}

source

  • host (string): the web host that wrote the file.
  • contestSlug, contestName (string): the shelf. The Mac app stamps them onto every imported review and shows them, so a file from the wrong shelf is plain at a glance.
  • exportedAt (date).

rubric

The shelf's active criteria, in order. Each has a slug, a name, a maxScore (int) and a weight (a number, which may be fractional).

  • slug is the key a criterion's score has in reviews[].scores: the criterion's name, lower-cased and hyphenated. When two criteria have the same name, the second slug ends in -<id>, so slugs stay unique and stable across exports.
  • weight is for display only. Weighted scores and percentages are left out of reviews[] entirely: they can be worked out from the score, the weight and the maximum, and a float in a score would be refused.

reviews[]

  • externalReviewId (string): django:<review id>. Stable across exports, so importing a newer export replaces a review instead of adding a second copy.
  • submissionRef: which entry the review is of.
  • nativeId is reserved and always null from the web.
  • externalId (string) is the entry number a judge sees. It is not unique: several entries can share "0".
  • sourcePk (int) is the web's id for the entry, and is unique.
  • title (string).
  • reviewerName (string). On a blind shelf it is Reviewer N, reviewerEmail is left out, and anonymized is true. The name is never in the file, rather than hidden by the reader.
  • reviewerEmail (string, optional): present only when anonymized is false.
  • submittedAt (date): when the review was last saved.
  • scores (object): criterion slug to an int (or a string, for a text criterion). A score for a criterion that has since been switched off is left out.
  • scoreComments (object): criterion slug to the reviewer's comment on that criterion. Only criteria with a comment appear.
  • assessment, moveForward, confidence (int or null), recommendation (string or null; a value from the shelf's recommendation scale, such as accept or minor_revision), comment (string or null), hasCoi (bool), coiDescription (string or null), complete (bool): the web review's own fields.

How the Mac app imports it

  • It refuses the whole file when formatVersion is higher than 1 or kind is not textshelf-review-inbox.
  • It skips one bad review, not the file. A review it cannot read is counted and reported; a float score drops that one score with a warning.
  • It matches each review to an entry by submissionRef.nativeId, then externalId, then a title that exactly one entry has (ignoring case). Anything else stays unmatched and is reported with the reason; a review is never attached to a guess.
  • Importing again replaces, keyed by externalReviewId, and the summary counts additions and updates separately.
  • Imported reviews never change an entry's status. They are someone else's reading, not the shelf owner's.
  • Scores keep their own labels. Each imported review stores the rubric entries for its scores (without weight), so a score reads "Innovation 4/5" whatever the shelf's own rubric is called. The shelf's rubric is never changed by an import. A score above its criterion's maxScore is reported, not clamped.

What the Mac app stores

Imported reviews go in submissions/<id>/external-reviews.json inside the shelf bundle (shelf-bundle.md), never in reviews/, which holds the shelf owner's own reviews. The array is sorted by submittedAt, then externalReviewId, so saving twice writes the same bytes.

{
  "reviews" : [
    {
      "anonymized" : true,
      "assessment" : 4,
      "comment" : "Strong opening; pacing flags in the middle third.",
      "complete" : true,
      "confidence" : 3,
      "externalReviewId" : "django:1234",
      "hasCoi" : false,
      "importedAt" : "2026-08-03T17:12:44.031Z",
      "moveForward" : 2,
      "recommendation" : "accept",
      "reviewerName" : "Reviewer 2",
      "rubric" : [
        {
          "maxScore" : 5,
          "name" : "Innovation",
          "slug" : "innovation"
        },
        {
          "maxScore" : 5,
          "name" : "Craft",
          "slug" : "craft"
        }
      ],
      "scoreComments" : {
        "innovation" : "Fresh premise."
      },
      "scores" : {
        "craft" : 3,
        "innovation" : 4
      },
      "source" : {
        "contestName" : "Spring 2026 Playwriting Prize",
        "contestSlug" : "spring-2026-playwriting",
        "exportedAt" : "2026-08-03T17:04:00.000Z",
        "host" : "textshelf.com"
      },
      "submittedAt" : "2026-08-01T09:30:00.000Z"
    }
  ],
  "schemaVersion" : 1
}

Unlike the inbox file, this file is strict: if it is present and cannot be read, the shelf will not open. The app rewrites it on every save, so a review skipped on reading would be lost at the next save.


This specification is licensed under CC BY 4.0, and its example and schema files under the MIT licence. Anyone may implement these formats in their own software. CC BY 4.0