Early developmentNot everything described here has shipped yet.

Skip to content

Subject JSON Format

Subject data is stored as JSON. The REST API returns and accepts the same shapes; the fields each endpoint adds or ignores are under REST API.

For Subject, Statement, and Value, see the Glossary.

Top-level structure

A page holds one optional main Subject and zero or more child Subjects (ADR 007), all in one subjects map with mainSubject pointing at the main one.

json
{
  "mainSubject": "<subject-id>",
  "subjects": {
    "<subject-id>": { ... }
  }
}
FieldTypeRequiredDescription
mainSubjectstringNoID of the page's main Subject. Omitted or null when the page has none.
subjectsobjectNoMap of Subject ID to Subject object. Omitted or empty when the page has no Subjects.

Subject object

json
{
  "label": "Professional Wiki GmbH",
  "schema": "Company",
  "statements": {
    "<property-name>": { ... }
  }
}
FieldTypeRequiredDescription
labelstringYesHuman-readable label for the Subject.
schemastringYesName of the Schema the Subject follows (a page in the Schema namespace).
statementsobjectNoMap of property name to Statement object. Omitted when the Subject has none.

A property mapped to null instead of a Statement object is skipped when the JSON is read.

Statement object

json
{
  "propertyType": "number",
  "value": 2019
}
FieldTypeRequiredDescription
propertyTypestringYesThe property's type when the value was written — the writer's schema (ADR 011).
valuevariesYesThe value, shaped by propertyType. See Value formats.

Value formats

propertyType holds the property type name, which fixes the value shape:

propertyTypevalue
text, url, select, date, dateTimeArray of strings, one per value part.
numberA single number (integer or float).
booleanA single boolean.
relationArray of relation objects.

A multi-part text value:

json
{ "propertyType": "text", "value": [ "First value", "Second value" ] }

Every registered PropertyType uses one of these four value shapes. A propertyType whose PropertyType is not registered — its extension disabled — keeps the raw value that was stored (unregistered-type).

Relations

Each relation value is an array of objects pointing at other Subjects:

json
{
  "propertyType": "relation",
  "value": [
    { "id": "r1demo5rrrrrrr1", "target": "s1demo4sssssss1" }
  ]
}
FieldTypeRequiredDescription
idstringYesID of this relation.
targetstringYesID of the target Subject.
propertiesobjectNoKey-value relation properties. Present only when non-empty.

With relation properties:

json
{
  "id": "r1demo5rrrrrrr1",
  "target": "s1demo4sssssss1",
  "properties": {
    "role": "CEO",
    "since": 2019
  }
}

IDs

Subject and Relation IDs are 15-character nanoid-style strings, lexicographically sortable by creation time. Subject IDs start with s (s1demo5sssssss1), Relation IDs with r (r1demo5rrrrrrr1). See ADR 014.

REST API

Reading Subjects

GET /rest.php/neowiki/v0/subject/{subjectId} returns a top-level requestedId and a subjects map; each Subject gains an id field.

  • ?expand=page adds pageId, pageTitle, and pageNamespaceId to each Subject. pageTitle is the full page title with namespace prefix (e.g. Help:Installation); pageNamespaceId is the canonical MediaWiki namespace ID (e.g. 0 for the main namespace, 12 for Help).
  • ?expand=relations embeds the Subjects this one's relations target; see REST API for the shape.
  • ?revisionId= returns the Subject as of that MediaWiki revision; an unknown or unreadable revision returns 404.

Creating Subjects

POST /rest.php/neowiki/v0/page/{pageId}/mainSubject and .../childSubjects create a Subject on a page. The body takes label, schema, and statements (all required), plus an optional comment edit summary.

The server mints the Subject ID unless you pass one:

FieldRequiredNotes
idNoSubject ID to assign. Well-formed (400 otherwise) and unused (409 otherwise). Pre-mint a batch with POST /rest.php/neowiki/v0/subject-ids to wire relations before their targets exist.

Writing Subjects

PUT /rest.php/neowiki/v0/subject/{subjectId} replaces the Subject's label and statements:

json
{
  "label": "Updated Label",
  "statements": {
    "Founded at": {
      "propertyType": "number",
      "value": 2019
    }
  },
  "comment": "Optional edit summary"
}
FieldRequiredNotes
labelYesNon-empty after trim.
statementsYesMap of property name to Statement; omitted names are deleted. Pass {} to clear all.
commentNoEdit summary.

On every endpoint that takes statements, an entry without propertyType, or whose value is empty for its type, is dropped without error. A value whose shape does not match its propertyType (see Value formats) is rejected with 400. For schema/value validation outcomes see Validation Codes.

A relation may omit id; the server generates one. The Subject's id, schema, and page fields are immutable and ignored if sent.

Writing one Statement

PUT /rest.php/neowiki/v0/subject/{subjectId}/statements/{propertyName} sets a single Statement, leaving the Subject's label and its other Statements as they are:

json
{
  "statement": {
    "propertyType": "url",
    "value": ["https://example.com"]
  },
  "comment": "Optional edit summary"
}
FieldRequiredNotes
statementYesA Statement object carrying a value. propertyType may be omitted, and then takes the type the Subject's Schema currently gives the property; a property the Schema does not define is rejected with 400. A value that is empty for its type removes the Statement.
commentNoEdit summary.

DELETE on the same path removes the Statement and takes only comment. Removing a Statement the Subject does not have succeeds and changes nothing.

Violations in the response cover the whole Subject, not only the property written.

{propertyName} is URL-encoded, so Founded at is Founded%20at. A property name containing / is not addressable this way on servers that reject %2F in paths; write those Statements with PUT /subject/{subjectId}.

Write responses

Creating a Subject, writing one, and writing one of its Statements all answer with the Subject as persisted and the Schema it instantiates, so a client does not have to re-read after a write:

json
{
  "status": "updated",
  "subjectId": "s1demo5sssssss1",
  "violations": [],
  "subject": { "id": "s1demo5sssssss1", "label": "Updated Label", "schema": "Company", "pageId": 42,
               "pageTitle": "Help:Installation", "pageNamespaceId": 12, "statements": {} },
  "schema": { "description": "A company", "propertyDefinitions": {} }
}

subject is the same shape GET /subject/{subjectId}?expand=page serves, so it reflects any normalisation the write applied. Its page fields are omitted when the server cannot resolve the Subject's page.

schema carries no name of its own — that is the Subject's schema field — and is absent when the Schema does not exist or its page is unreadable. It is the Schema as of the write, which may define properties the client's copy does not.

Complete example

A page about Berlin with a main Subject and a child Subject for population data:

json
{
  "mainSubject": "s1demo2sssssss1",
  "subjects": {
    "s1demo2sssssss1": {
      "label": "Berlin",
      "schema": "City",
      "statements": {
        "Country": {
          "propertyType": "text",
          "value": ["Germany"]
        }
      }
    },
    "s1demo2sssssss2": {
      "label": "Latest",
      "schema": "Population",
      "statements": {
        "Population": {
          "propertyType": "number",
          "value": 3677472
        },
        "Date": {
          "propertyType": "text",
          "value": ["2020-12-31"]
        },
        "References": {
          "propertyType": "url",
          "value": ["https://example.com/Pop2020"]
        }
      }
    }
  }
}