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).
slugis the key a criterion's score has inreviews[].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.weightis for display only. Weighted scores and percentages are left out ofreviews[]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.nativeIdis reserved and alwaysnullfrom 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 isReviewer N,reviewerEmailis left out, andanonymizedistrue. The name is never in the file, rather than hidden by the reader.reviewerEmail(string, optional): present only whenanonymizedisfalse.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 ornull),recommendation(string ornull; a value from the shelf's recommendation scale, such asacceptorminor_revision),comment(string ornull),hasCoi(bool),coiDescription(string ornull),complete(bool): the web review's own fields.
How the Mac app imports it
- It refuses the whole file when
formatVersionis higher than 1 orkindis nottextshelf-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, thenexternalId, 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
rubricentries for its scores (withoutweight), 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'smaxScoreis 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