openapi: 3.0.0
info:
  title: Worlds API
  version: 1.0.0
  description: >
    API for managing and querying RDF-based worlds.


    **Namespaces:** Routes under `/namespaces/{namespace}/...` target a specific
    tenant namespace.

    The path segment `_` is reserved: it expands to the authenticated tenant
    default namespace

    (or the platform namespace when no tenant applies). Shorthand routes under
    `/worlds/...`

    use the default namespace and omit the `/namespaces/...` prefix.
servers:
  - url: https://api.wazoo.dev
    description: API
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  responses:
    BadRequest:
      description: Bad Request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Not Found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Forbidden
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    WorldSource:
      description: >
        Target world: a qualified string (`namespace/slug`), `{ slug, namespace?
        }`, or `{ name }`

        (same qualified form as `name`).
      oneOf:
        - type: string
        - type: object
          required:
            - slug
          properties:
            slug:
              type: string
            namespace:
              type: string
            write:
              type: boolean
            schema:
              type: boolean
        - type: object
          required:
            - name
          properties:
            name:
              type: string
            write:
              type: boolean
            schema:
              type: boolean
    World:
      type: object
      properties:
        slug:
          type: string
        namespace:
          type: string
          description: Parent namespace for this world in multitenancy contexts.
        label:
          type: string
        description:
          type: string
          nullable: true
        createdAt:
          type: number
        updatedAt:
          type: number
        deletedAt:
          type: number
          nullable: true
      required:
        - slug
        - createdAt
        - updatedAt
    CreateWorldParams:
      type: object
      properties:
        slug:
          type: string
        namespace:
          type: string
          description: >-
            Optional in body; the path namespace wins for `/namespaces/...`
            routes. The reserved `_` value is rejected here.
        label:
          type: string
        description:
          type: string
          nullable: true
      required:
        - slug
    UpdateWorldParams:
      type: object
      properties:
        source:
          $ref: '#/components/schemas/WorldSource'
          description: Optional; defaults to the world identified by the URL path.
        label:
          type: string
        description:
          type: string
          nullable: true
    ExportPostBody:
      type: object
      properties:
        contentType:
          type: string
          description: >-
            RDF serialization MIME type. If omitted, the server negotiates from
            `Accept` (default `application/n-quads`).
        source:
          $ref: '#/components/schemas/WorldSource'
          description: Optional; defaults to the world identified by the URL path.
    ImportPostBody:
      type: object
      required:
        - data
      properties:
        data:
          type: string
          description: Base64-encoded RDF document.
        contentType:
          type: string
          default: application/n-quads
        source:
          $ref: '#/components/schemas/WorldSource'
          description: Optional; defaults to the world identified by the URL path.
    SparqlPostBody:
      type: object
      properties:
        query:
          type: string
          description: >
            SPARQL 1.1 query or update string. If omitted or empty, the server
            returns a SPARQL 1.1

            service description (RDF), with serialization negotiated from
            `Accept`.
        sources:
          type: array
          items:
            $ref: '#/components/schemas/WorldSource'
        namespace:
          type: string
        defaultGraphUris:
          type: array
          items:
            type: string
        namedGraphUris:
          type: array
          items:
            type: string
    SearchPostBody:
      type: object
      required:
        - query
      properties:
        query:
          type: string
        sources:
          type: array
          items:
            $ref: '#/components/schemas/WorldSource'
        namespace:
          type: string
        limit:
          type: integer
          minimum: 1
        subjects:
          type: array
          items:
            type: string
        predicates:
          type: array
          items:
            type: string
        types:
          type: array
          items:
            type: string
    DeleteWorldBody:
      type: object
      properties:
        source:
          $ref: '#/components/schemas/WorldSource'
          description: Optional; defaults to the world identified by the URL path.
    TripleSearchResult:
      type: object
      properties:
        subject:
          type: string
        predicate:
          type: string
        object:
          type: string
        vecRank:
          type: number
          nullable: true
        ftsRank:
          type: number
          nullable: true
        score:
          type: number
        world:
          $ref: '#/components/schemas/World'
      required:
        - subject
        - predicate
        - object
        - vecRank
        - ftsRank
        - score
        - world
    SparqlResults:
      type: object
      oneOf:
        - $ref: '#/components/schemas/SparqlSelectResults'
        - $ref: '#/components/schemas/SparqlAskResults'
        - $ref: '#/components/schemas/SparqlQuadsResults'
    SparqlSelectResults:
      type: object
      properties:
        head:
          $ref: '#/components/schemas/SparqlHead'
        results:
          type: object
          properties:
            bindings:
              type: array
              items:
                $ref: '#/components/schemas/SparqlBinding'
      required:
        - head
        - results
    SparqlAskResults:
      type: object
      properties:
        head:
          $ref: '#/components/schemas/SparqlHead'
        boolean:
          type: boolean
      required:
        - head
        - boolean
    SparqlQuadsResults:
      type: object
      properties:
        head:
          $ref: '#/components/schemas/SparqlHead'
        results:
          type: object
          properties:
            quads:
              type: array
              items:
                $ref: '#/components/schemas/SparqlQuad'
      required:
        - head
        - results
    SparqlHead:
      type: object
      properties:
        vars:
          type: array
          items:
            type: string
        link:
          type: array
          items:
            type: string
          nullable: true
    SparqlBinding:
      type: object
      additionalProperties:
        $ref: '#/components/schemas/SparqlValue'
    SparqlValue:
      oneOf:
        - $ref: '#/components/schemas/SparqlUriValue'
        - $ref: '#/components/schemas/SparqlBnodeValue'
        - $ref: '#/components/schemas/SparqlLiteralValue'
        - $ref: '#/components/schemas/SparqlTripleValue'
      discriminator:
        propertyName: type
    SparqlUriValue:
      type: object
      properties:
        type:
          enum:
            - uri
        value:
          type: string
      required:
        - type
        - value
    SparqlBnodeValue:
      type: object
      properties:
        type:
          enum:
            - bnode
        value:
          type: string
      required:
        - type
        - value
    SparqlLiteralValue:
      type: object
      properties:
        type:
          enum:
            - literal
        value:
          type: string
        xml:lang:
          type: string
        datatype:
          type: string
      required:
        - type
        - value
    SparqlTripleValue:
      type: object
      properties:
        type:
          enum:
            - triple
        value:
          type: object
          properties:
            subject:
              $ref: '#/components/schemas/SparqlValue'
            predicate:
              $ref: '#/components/schemas/SparqlValue'
            object:
              $ref: '#/components/schemas/SparqlValue'
          required:
            - subject
            - predicate
            - object
      required:
        - type
        - value
    SparqlQuad:
      type: object
      properties:
        subject:
          $ref: '#/components/schemas/SparqlSubject'
        predicate:
          $ref: '#/components/schemas/SparqlPredicate'
        object:
          $ref: '#/components/schemas/SparqlValue'
        graph:
          $ref: '#/components/schemas/SparqlGraph'
    SparqlSubject:
      type: object
      properties:
        type:
          enum:
            - uri
            - bnode
        value:
          type: string
      required:
        - type
        - value
    SparqlPredicate:
      type: object
      properties:
        type:
          enum:
            - uri
        value:
          type: string
      required:
        - type
        - value
    SparqlGraph:
      type: object
      properties:
        type:
          enum:
            - default
            - uri
        value:
          type: string
      required:
        - type
        - value
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            message:
              type: string
      required:
        - error
security:
  - bearerAuth: []
paths:
  /worlds:
    get:
      operationId: listWorldsDefaultNamespace
      summary: List worlds (default namespace)
      description: Lists worlds in the default namespace for the authenticated principal.
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: pageSize
          in: query
          schema:
            type: integer
            maximum: 100
            default: 20
      responses:
        '200':
          description: Worlds in the namespace
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/World'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createWorldDefaultNamespace
      summary: Create a world (default namespace)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWorldParams'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/World'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /worlds/{slug}:
    parameters:
      - name: slug
        in: path
        required: true
        schema:
          type: string
    get:
      operationId: getWorldDefaultNamespace
      summary: Get a world (default namespace)
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/World'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      operationId: updateWorldDefaultNamespace
      summary: Update a world (default namespace)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWorldParams'
      responses:
        '204':
          description: No Content
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteWorldDefaultNamespace
      summary: Delete a world (default namespace)
      description: >-
        Optional JSON body can override the target `source`; otherwise the path
        identifies the world.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeleteWorldBody'
      responses:
        '204':
          description: No Content
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /worlds/{slug}/export:
    post:
      operationId: exportWorldDefaultNamespace
      summary: Export RDF from a world (default namespace)
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExportPostBody'
      responses:
        '200':
          description: Exported RDF (per `contentType` or negotiated `Accept`)
          content:
            text/turtle:
              schema:
                type: string
                format: binary
            application/n-quads:
              schema:
                type: string
                format: binary
            application/n-triples:
              schema:
                type: string
                format: binary
            text/n3:
              schema:
                type: string
                format: binary
            application/trig:
              schema:
                type: string
                format: binary
            application/ld+json:
              schema:
                type: string
                format: binary
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /worlds/{slug}/import:
    post:
      operationId: importWorldDefaultNamespace
      summary: Import RDF into a world (default namespace)
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImportPostBody'
      responses:
        '204':
          description: No Content
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /worlds/sparql:
    post:
      operationId: sparqlCollectionDefaultNamespace
      summary: SPARQL over the default namespace collection
      description: >
        JSON body matches `WorldsSparqlInput`. Path scope supplies the default
        namespace; no world slug is implied.

        Omit or leave `query` empty to retrieve a SPARQL 1.1 service description
        (RDF).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SparqlPostBody'
      responses:
        '200':
          description: >-
            SPARQL JSON results, or RDF service description when `query` is
            absent
          content:
            application/sparql-results+json:
              schema:
                $ref: '#/components/schemas/SparqlResults'
            text/turtle:
              schema:
                type: string
            application/rdf+xml:
              schema:
                type: string
            application/trig:
              schema:
                type: string
            application/ld+json:
              schema:
                type: string
        '204':
          description: SPARQL update succeeded with no result body
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /worlds/{slug}/sparql:
    post:
      operationId: sparqlUnaryDefaultNamespace
      summary: SPARQL scoped to one world (default namespace)
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SparqlPostBody'
      responses:
        '200':
          description: >-
            SPARQL JSON results, or RDF service description when `query` is
            absent
          content:
            application/sparql-results+json:
              schema:
                $ref: '#/components/schemas/SparqlResults'
            text/turtle:
              schema:
                type: string
            application/rdf+xml:
              schema:
                type: string
            application/trig:
              schema:
                type: string
            application/ld+json:
              schema:
                type: string
        '204':
          description: SPARQL update succeeded with no result body
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /worlds/search:
    post:
      operationId: searchCollectionDefaultNamespace
      summary: Search triples in the default namespace collection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SearchPostBody'
      responses:
        '200':
          description: Ranked triple hits
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TripleSearchResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /worlds/{slug}/search:
    post:
      operationId: searchUnaryDefaultNamespace
      summary: Search triples scoped to one world (default namespace)
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SearchPostBody'
      responses:
        '200':
          description: Ranked triple hits
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TripleSearchResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /namespaces/{namespace}/worlds:
    parameters:
      - name: namespace
        in: path
        required: true
        schema:
          type: string
        description: Target namespace, or `_` for the caller default namespace.
    get:
      operationId: listWorldsNamespaced
      summary: List worlds in a namespace
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: pageSize
          in: query
          schema:
            type: integer
            maximum: 100
            default: 20
      responses:
        '200':
          description: Worlds in the namespace
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/World'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      operationId: createWorldNamespaced
      summary: Create a world in a namespace
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWorldParams'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/World'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /namespaces/{namespace}/worlds/{slug}:
    parameters:
      - name: namespace
        in: path
        required: true
        schema:
          type: string
        description: Target namespace, or `_` for the caller default namespace.
      - name: slug
        in: path
        required: true
        schema:
          type: string
    get:
      operationId: getWorldNamespaced
      summary: Get a world
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/World'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      operationId: updateWorldNamespaced
      summary: Update a world
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWorldParams'
      responses:
        '204':
          description: No Content
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteWorldNamespaced
      summary: Delete a world
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeleteWorldBody'
      responses:
        '204':
          description: No Content
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /namespaces/{namespace}/worlds/{slug}/export:
    post:
      operationId: exportWorldNamespaced
      summary: Export RDF from a world
      parameters:
        - name: namespace
          in: path
          required: true
          schema:
            type: string
        - name: slug
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExportPostBody'
      responses:
        '200':
          description: Exported RDF
          content:
            text/turtle:
              schema:
                type: string
                format: binary
            application/n-quads:
              schema:
                type: string
                format: binary
            application/n-triples:
              schema:
                type: string
                format: binary
            text/n3:
              schema:
                type: string
                format: binary
            application/trig:
              schema:
                type: string
                format: binary
            application/ld+json:
              schema:
                type: string
                format: binary
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /namespaces/{namespace}/worlds/{slug}/import:
    post:
      operationId: importWorldNamespaced
      summary: Import RDF into a world
      parameters:
        - name: namespace
          in: path
          required: true
          schema:
            type: string
        - name: slug
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImportPostBody'
      responses:
        '204':
          description: No Content
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /namespaces/{namespace}/worlds/sparql:
    post:
      operationId: sparqlCollectionNamespaced
      summary: SPARQL over all worlds in a namespace
      parameters:
        - name: namespace
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SparqlPostBody'
      responses:
        '200':
          description: >-
            SPARQL JSON results, or RDF service description when `query` is
            absent
          content:
            application/sparql-results+json:
              schema:
                $ref: '#/components/schemas/SparqlResults'
            text/turtle:
              schema:
                type: string
            application/rdf+xml:
              schema:
                type: string
            application/trig:
              schema:
                type: string
            application/ld+json:
              schema:
                type: string
        '204':
          description: SPARQL update succeeded with no result body
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /namespaces/{namespace}/worlds/{slug}/sparql:
    post:
      operationId: sparqlUnaryNamespaced
      summary: SPARQL scoped to one world
      parameters:
        - name: namespace
          in: path
          required: true
          schema:
            type: string
        - name: slug
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SparqlPostBody'
      responses:
        '200':
          description: >-
            SPARQL JSON results, or RDF service description when `query` is
            absent
          content:
            application/sparql-results+json:
              schema:
                $ref: '#/components/schemas/SparqlResults'
            text/turtle:
              schema:
                type: string
            application/rdf+xml:
              schema:
                type: string
            application/trig:
              schema:
                type: string
            application/ld+json:
              schema:
                type: string
        '204':
          description: SPARQL update succeeded with no result body
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /namespaces/{namespace}/worlds/search:
    post:
      operationId: searchCollectionNamespaced
      summary: Search triples across a namespace
      parameters:
        - name: namespace
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SearchPostBody'
      responses:
        '200':
          description: Ranked triple hits
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TripleSearchResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /namespaces/{namespace}/worlds/{slug}/search:
    post:
      operationId: searchUnaryNamespaced
      summary: Search triples scoped to one world
      parameters:
        - name: namespace
          in: path
          required: true
          schema:
            type: string
        - name: slug
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SearchPostBody'
      responses:
        '200':
          description: Ranked triple hits
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TripleSearchResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
