{"openapi":"3.1.0","info":{"title":"Semaptic Hub API","version":"1.0.0","summary":"Self-describing JSON and RDF from a shared energy vocabulary.","description":"Name the fields of a JSON document with terms of the SEMAPTIC vocabulary and the hub supplies their meaning: it embeds the @semaptic object that describes every value, and converts the document to RDF. The vocabulary itself — its terms, categories, prefixes and templates — can be read here too.\n\nThe document endpoints send CORS headers, so a page on any site can call them, and are rate-limited per client.","contact":{"name":"INESC TEC — Centre for Power and Energy Systems","url":"https://semaptichub.inesctec.pt"},"license":{"name":"Vocabulary: CC BY 4.0","url":"https://creativecommons.org/licenses/by/4.0/"}},"servers":[{"url":"https://semaptichub.inesctec.pt","description":"The Semaptic Hub"}],"security":[],"tags":[{"name":"Documents","description":"Embed @semaptic in a JSON document, and convert it to RDF."},{"name":"Vocabulary","description":"The terms of the vocabulary, their categories, prefixes and templates."},{"name":"Suggestions","description":"Propose a term the vocabulary lacks."}],"paths":{"/api/semaptic":{"post":{"tags":["Documents"],"operationId":"embedSemaptic","summary":"Embed the @semaptic object in a document","description":"Every key that is a vocabulary term gets the term's template from the registry, with the templates of the nodes it introduces and the prefixes they use. Every other key gets an empty entry (\"key\": {}), to be described by hand; until it is, it converts as a plain literal. An existing @semaptic object is replaced. @annotations records the date and that the API assembled the block.","parameters":[{"name":"base","in":"query","description":"The namespace the document's entities are minted in: an absolute http(s) IRI ending in / or #.","schema":{"type":"string","format":"uri"},"example":"https://data.example.org/id/"},{"name":"report","in":"query","description":"Return { document, report } rather than the document alone.","schema":{"type":"boolean","default":false}}],"requestBody":{"required":true,"description":"The document, up to 10 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"}}}},"responses":{"200":{"description":"The document with its @semaptic object — or, with report=true, { document, report }.","headers":{"X-Semaptic-Described":{"description":"How many keys took their template from the registry.","schema":{"type":"integer"}},"X-Semaptic-Undescribed":{"description":"The keys left with an empty entry, percent-encoded and comma-separated.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/DocumentWithSemaptic"},{"type":"object","properties":{"document":{"$ref":"#/components/schemas/DocumentWithSemaptic"},"report":{"$ref":"#/components/schemas/EmbedReport"}}}]}}}},"400":{"description":"Not JSON, not a JSON object, or an invalid base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Larger than 10 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests from this client; see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The vocabulary registry is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/rdf":{"post":{"tags":["Documents"],"operationId":"convertToRdf","summary":"Convert a document carrying @semaptic to RDF","description":"The conversion the RDF Transformer runs, with the hub's datatypes. The document must carry its @semaptic object — send it to /api/semaptic first, or use /api/json-to-rdf.","parameters":[{"name":"format","in":"query","description":"The RDF format. Without it, the Accept header decides; Turtle by default.","schema":{"type":"string","enum":["ttl","nt","rdf","jsonld"],"default":"ttl"}},{"name":"base","in":"query","description":"The namespace the document's entities are minted in: an absolute http(s) IRI ending in / or #.","schema":{"type":"string","format":"uri"},"example":"https://data.example.org/id/"},{"name":"skolemize","in":"query","description":"Name anonymous nodes with stable IRIs, so that the graphs of several documents merge.","schema":{"type":"boolean","default":false}},{"name":"align","in":"query","description":"Link values to their instant (time:hasTime), from a time axis or a record's own timestamp.","schema":{"type":"boolean","default":true}},{"name":"maxArrayItems","in":"query","description":"Keep at most this many elements of each array (0 = all): to convert a sample of a large document.","schema":{"type":"integer","minimum":0,"default":0}}],"requestBody":{"required":true,"description":"The document with its @semaptic object, up to 5 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentWithSemaptic"}}}},"responses":{"200":{"description":"The RDF.","headers":{"X-Semaptic-Triples":{"description":"How many triples the RDF holds.","schema":{"type":"integer"}},"X-Semaptic-Warnings":{"description":"How many warnings the conversion raised.","schema":{"type":"integer"}},"X-Semaptic-Unmapped":{"description":"Keys kept as plain literals, percent-encoded and comma-separated.","schema":{"type":"string"}}},"content":{"text/turtle":{"schema":{"type":"string"}},"application/n-triples":{"schema":{"type":"string"}},"application/rdf+xml":{"schema":{"type":"string"}},"application/ld+json":{"schema":{"type":"object"}}}},"400":{"description":"Not JSON, no @semaptic object, or an invalid option.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Larger than 5 MB, or more than 2 million triples: convert a sample with maxArrayItems.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The document could not be converted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests from this client; see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Busy converting other documents; see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/json-to-rdf":{"post":{"tags":["Documents"],"operationId":"jsonToRdf","summary":"Convert a plain document to RDF, when every key is a vocabulary term","description":"The @semaptic object is assembled from the registry and the document converted in one step, as /api/semaptic then /api/rdf would. Any @semaptic object the document carries is ignored. When a key is not a vocabulary term, nothing is converted and the answer names the keys.","parameters":[{"name":"format","in":"query","description":"The RDF format. Without it, the Accept header decides; Turtle by default.","schema":{"type":"string","enum":["ttl","nt","rdf","jsonld"],"default":"ttl"}},{"name":"base","in":"query","description":"The namespace the document's entities are minted in: an absolute http(s) IRI ending in / or #.","schema":{"type":"string","format":"uri"},"example":"https://data.example.org/id/"},{"name":"skolemize","in":"query","description":"Name anonymous nodes with stable IRIs, so that the graphs of several documents merge.","schema":{"type":"boolean","default":false}},{"name":"align","in":"query","description":"Link values to their instant (time:hasTime), from a time axis or a record's own timestamp.","schema":{"type":"boolean","default":true}},{"name":"maxArrayItems","in":"query","description":"Keep at most this many elements of each array (0 = all): to convert a sample of a large document.","schema":{"type":"integer","minimum":0,"default":0}}],"requestBody":{"required":true,"description":"The document, up to 5 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"}}}},"responses":{"200":{"description":"The RDF.","headers":{"X-Semaptic-Triples":{"description":"How many triples the RDF holds.","schema":{"type":"integer"}},"X-Semaptic-Warnings":{"description":"How many warnings the conversion raised.","schema":{"type":"integer"}},"X-Semaptic-Unmapped":{"description":"Keys kept as plain literals, percent-encoded and comma-separated.","schema":{"type":"string"}},"X-Semaptic-Described":{"description":"How many keys took their template from the registry.","schema":{"type":"integer"}}},"content":{"text/turtle":{"schema":{"type":"string"}},"application/n-triples":{"schema":{"type":"string"}},"application/rdf+xml":{"schema":{"type":"string"}},"application/ld+json":{"schema":{"type":"object"}}}},"400":{"description":"Not JSON, not a JSON object, or an invalid option.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Larger than 5 MB, or more than 2 million triples.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Not every key is a vocabulary term: nothing was converted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotAllTerms"}}}},"429":{"description":"Too many requests from this client; see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The registry is unavailable, or the converter is busy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/parameters":{"get":{"tags":["Vocabulary"],"operationId":"listTerms","summary":"The vocabulary's terms","parameters":[{"name":"category","in":"query","description":"Only the terms in this category.","schema":{"type":"string"}}],"responses":{"200":{"description":"The terms, by name.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"count":{"type":"integer"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Term"}}}}}}},"500":{"description":"The registry is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/categories":{"get":{"tags":["Vocabulary"],"operationId":"listCategories","summary":"The categories terms are grouped in","responses":{"200":{"description":"The categories.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"count":{"type":"integer"},"data":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"}}}}}}}}},"500":{"description":"The registry is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/ontologies":{"get":{"tags":["Vocabulary"],"operationId":"listPrefixes","summary":"The registered prefixes and their namespaces","responses":{"200":{"description":"Prefix and namespace IRI of every ontology the templates may cite.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"prefix":{"type":"string"},"ontology":{"type":"string","format":"uri"}}}}}}},"500":{"description":"The registry is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/semantics":{"get":{"tags":["Vocabulary"],"operationId":"termTemplates","summary":"The templates of one or more terms, as rows","description":"Each row is one statement of a term's template (subject, predicate, object), including the rows of the nodes it introduces.","parameters":[{"name":"parameter","in":"query","required":true,"description":"A term; repeat for several (at most 500).","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true}],"responses":{"200":{"description":"The rows.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SemanticsRow"}}}}},"400":{"description":"No term given, or more than 500.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The registry is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/vocabulary":{"get":{"tags":["Vocabulary"],"operationId":"vocabularyRdf","summary":"The whole vocabulary as RDF","description":"Also served at the vocabulary's namespace, https://semaptichub.inesctec.pt/vocabulary/, to requests that prefer RDF.","parameters":[{"name":"format","in":"query","description":"Without it, the Accept header decides; Turtle by default.","schema":{"type":"string","enum":["ttl","nt","rdf"],"default":"ttl"}}],"responses":{"200":{"description":"The vocabulary.","content":{"text/turtle":{"schema":{"type":"string"}},"application/n-triples":{"schema":{"type":"string"}},"application/rdf+xml":{"schema":{"type":"string"}}}}}}},"/api/suggest-parameter":{"post":{"tags":["Suggestions"],"operationId":"suggestTerm","summary":"Suggest a term for the vocabulary","description":"Stored for an administrator to review; the email is used only to follow up on the suggestion.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["parameter","description","datatype","email"],"properties":{"parameter":{"type":"string","description":"The proposed name, after the naming conventions.","example":"supplyVoltageValue-V"},"description":{"type":"string"},"datatype":{"type":"string","example":"xsd:float"},"units":{"type":"string","example":"V"},"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Saved for review.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"400":{"description":"A field is missing, the email is invalid, or the term already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already suggested and waiting for review.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The suggestion could not be saved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Document":{"type":"object","description":"A JSON object: the service's own data, keys as the service writes them.","additionalProperties":true,"example":{"energyConsumerID":"c1","activeEnergyConsumedValue-kWh":0.4}},"DocumentWithSemaptic":{"type":"object","description":"A JSON object carrying its @semaptic object (@prefix, @context, @annotations) beside the data.","required":["@semaptic"],"properties":{"@semaptic":{"$ref":"#/components/schemas/SemapticObject"}},"additionalProperties":true,"example":{"energyConsumerID":"c1","activeEnergyConsumedValue-kWh":0.4,"@semaptic":{"@prefix":{"@base":"https://data.example.org/id/","rdf":"http://www.w3.org/1999/02/22-rdf-syntax-ns#","cim":"http://iec.ch/TC57/CIM100#","saref":"https://saref.etsi.org/core/","qudt":"http://qudt.org/schema/qudt/","quantitykind":"http://qudt.org/vocab/quantitykind/","unit":"http://qudt.org/vocab/unit/","s4ener":"https://saref.etsi.org/saref4ener/"},"@context":{"energyConsumerID":{"rdf:type":"cim:EnergyConsumer","saref:hasIdentifier":"$energyConsumerID"},"activeEnergyConsumedValue-kWh":{"rdf:type":"qudt:Quantity","qudt:hasQuantityKind":"quantitykind:ActiveEnergy","qudt:hasUnit":"unit:KiloW-HR","qudt:numericValue":"$activeEnergyConsumedValue-kWh","s4ener:hasUsage":"s4ener:Consumption"}},"@annotations":{}}}},"SemapticObject":{"type":"object","description":"What the document's keys mean: the namespaces cited, one template per key, and dataset metadata.","properties":{"@prefix":{"type":"object","description":"prefix -> namespace IRI for every prefix the templates cite; \"@base\", when present, is the namespace the document's entities are minted in.","additionalProperties":{"type":"string","format":"uri"}},"@context":{"type":"object","description":"One template per key (and per node a template introduces, ?name): predicate -> object, where $key is the value, ?name a node and @key an entity. An empty template ({}) describes nothing.","additionalProperties":{"type":"object","additionalProperties":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}}},"@annotations":{"type":"object","description":"Dataset metadata, Dublin Core and DCAT properties -> values.","additionalProperties":{"type":"string"}}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"What was wrong, in words."}}},"EmbedReport":{"type":"object","properties":{"described":{"type":"array","items":{"type":"string"},"description":"Keys whose entry came from the registry."},"undescribed":{"type":"array","items":{"type":"string"},"description":"Keys written with an empty entry."},"notTerms":{"type":"array","items":{"type":"string"},"description":"Of those, keys that are not vocabulary terms."},"termsWithoutSemantics":{"type":"array","items":{"type":"string"},"description":"Of those, terms without semantics yet."},"undeclaredPrefixes":{"type":"array","items":{"type":"string"},"description":"Prefixes the templates use but no namespace declares."}}},"NotAllTerms":{"type":"object","properties":{"error":{"type":"string"},"notTerms":{"type":"array","items":{"type":"string"}},"termsWithoutSemantics":{"type":"array","items":{"type":"string"}},"hint":{"type":"string"}},"example":{"error":"Not every key of the document is a vocabulary term, so it was not converted.","notTerms":["meter_serial"],"termsWithoutSemantics":[],"hint":"Rename the keys to vocabulary terms (the Vocabulary Aligner), or describe them: POST the document to /api/semaptic, fill in the empty entries, then POST it to /api/rdf."}},"Term":{"type":"object","properties":{"parameter":{"type":"string","example":"activeEnergyConsumedValue-kWh"},"description":{"type":"string"},"datatype":{"type":"string","example":"xsd:float"}}},"SemanticsRow":{"type":"object","properties":{"parameter":{"type":"string"},"subject":{"type":"string","description":"The term, or a node it introduces (?name)."},"predicate":{"type":"string"},"object":{"type":"string","description":"A prefixed name, or a token: $key (the value), ?name (a node), @key (an entity)."}}}}}}