Ontology SDK
Java and other languages
The Maven artifact, typed Java classes, and generating a client from OpenAPI.
Java
Converge serves a Maven repository for each ontology. Add it to ~/.m2/settings.xml with your token as a header:
<settings>
<servers>
<server>
<id>converge</id>
<configuration>
<httpHeaders>
<property>
<name>Authorization</name>
<value>Bearer ${env.FOUNDRY_TOKEN}</value>
</property>
</httpHeaders>
</configuration>
</server>
</servers>
</settings>
Then add the repository and the dependency to your pom.xml:
<repositories>
<repository>
<id>converge</id>
<url>https://cnvrg.si/api/v2/osdk/maven/</url>
</repository>
</repositories>
<dependency>
<groupId>dev.converge</groupId>
<artifactId>converge-osdk-<ontology></artifactId>
<version>[1.0.0,)</version>
</dependency>
The artifact is generated from the ontology's OpenAPI document, which holds the API's routes only, so it is a plain API client, not typed object classes:
import dev.converge.osdk.ApiClient;
import dev.converge.osdk.api.OntologyV2Api;
ApiClient api = new ApiClient();
api.updateBaseUri("https://cnvrg.si/api");
api.setRequestInterceptor(r -> r.header("Authorization", "Bearer " + System.getenv("FOUNDRY_TOKEN")));
OntologyV2Api ontology = new OntologyV2Api(api);
Each endpoint of the ontology API is a method on OntologyV2Api. You pass object type and property names as strings, and objects come back as maps keyed by property name.
The artifact is built once per Converge server release and is the same for every caller. Versions look like 1.0.0-h<hash>, and a new version appears when the API's routes change. The server only serves its own version, so an exact pin stops resolving after such a release. Use the range [1.0.0,), which always picks the current one.
Typed object classes
For typed classes, download Java sources generated from the object types and action types you can see, and put them in your project:
curl -H "Authorization: Bearer $FOUNDRY_TOKEN" \
"https://cnvrg.si/api/v2/ontologies/<ontology>/osdk/java?objectTypes=Order&actionTypes=place_order" -o osdk-java.json
The answer's files map holds the sources under dev/converge/osdk/<ontology>/. They need Java 17 and Jackson (jackson-databind), which the Maven artifact above already brings. Every object type is a record, with its properties under Props:
import dev.converge.osdk.converge.*;
ConvergeClient c = new ConvergeClient("https://cnvrg.si/api", System.getenv("FOUNDRY_TOKEN"));
List<Order> big = Order.objects(c)
.where(Order.Props.totalValue.gt(100.0).and(Where.not(Order.Props.status.eq("closed"))))
.orderBy(Order.Props.totalValue, true)
.take(10);
Order one = Order.objects(c).get("o1");
JsonNode result = Actions.placeOrder(c, "o9", "c1", List.of("p1"), 3L);
Page<Customer> owners = Order.Links.placedBy(c, "o1").page(100, null);
long open = Order.objects(c).where(Order.Props.status.eq("open")).count();
JsonNode sums = Order.objects(c).aggregate("sum", Order.Props.totalValue, List.of("status"));
JsonNode score = Queries.customerLifetimeValue(c, "customer/c1");
Filters are eq, ne, gt, gte, lt, lte, range, regex, startsWith, containsAnyTerm, containsAllTerms, containsAllTermsInOrder, containsAllTermsInOrderPrefixLastTerm, isNull and isNotNull, combined with Where.and, Where.or and Where.not. ne also matches objects with no value. orderBy(prop, true) sorts descending, orderBy(prop, false) ascending. gt, gte, lt, lte and range take a number or an ISO 8601 date or timestamp string ("2026-01-31"); any other string answers 422. startsWith is case-sensitive, while the text search filters ignore case. An eq value made of letters, a /, then letters, digits, _ or - (such as "a/b") is read as an object reference and never matches a text property; for such a text value use regex with the value itself, which matches the whole value: Order.Props.orderId.regex("a/b"). A non-2xx answer throws ConvergeException with the status and the server's error body. The client refuses redirects, and refuses a plain-http base URL except for localhost, so your token only goes to the host you named. iterate(pageSize) walks every object, reading pages as it goes. For aggregate, group by a property name, ObjectSet.fixedWidth(prop, width), ObjectSet.ranges(prop, List.of(List.of(lo, hi))), ObjectSet.duration(prop, 1, "MONTHS") or ObjectSet.maxGroupCount(prop, 5). aggregate(List.of(ObjectSet.metric("count", null, null), ObjectSet.metric("sum", Order.Props.total, "total")), groupBy, "total", true) computes several metrics at once and orders the groups by one of them. The rules are the same as in TypeScript. nearestNeighbors(List.of(0.1, 0.2), 10, Doc.Props.embedding) returns the closest objects by cosine similarity, each with $score, and the set's filter applies (see Nearest neighbors in the TypeScript page). An action method returns the server's JSON. For an applied action that is validation and edits. An action that needs approval answers HTTP 202 with status ("pending_approval"), approval_id, action and requested_by, and has not changed anything yet, so check result.path("status").asText() first. Descriptions from the ontology metadata appear as Javadoc on the classes, properties and actions. Each action also gets a batch method: Actions.placeOrderBatch(c, List.of(new Actions.PlaceOrderRequest(...), ...)) applies 1 to 100 requests. Each object type has its link types under Links; a link to a type you did not download comes back as JsonNode. Queries are methods on Queries and return the query's value. A parameter is required unless the type says otherwise, and a missing required one throws before anything is sent.
Media
Each media reference property gets a reader and an upload under Media:
MediaItem photo = Order.Media.photo(c, "o1");
try (InputStream in = photo.openStream()) { Files.copy(in, Path.of("o1.png")); }
JsonNode meta = photo.fetchMetadata(); // path, sizeBytes, mediaType
JsonNode ref = Order.Media.uploadPhoto(c, pngBytes, "orders/o9.png");
Pass the returned reference to an action that writes the property. The server checks that you can read the object (its visibility, markings and masking) and the media set. The content is untrusted, because the uploader chose its type. Showing an image, video or audio in an <img>, <video> or <audio> element is fine; never open other content (HTML, SVG) as a blob: or data: URL in a frame, tab or link, where it would run on your app's origin.
Other languages
Every ontology has its own OpenAPI document. It describes the routes of the ontology API (object types, objects, links, actions, queries and aggregations), cut to that ontology and shaped for client generators:
curl -H "Authorization: Bearer $FOUNDRY_TOKEN" \
https://cnvrg.si/api/v2/ontologies/<ontology>/openapi.json -o ontology.json
The document is the route table only, and it is the same for every caller. It holds no object types, properties, actions or queries. A client generated from it takes type and property names as strings and returns objects as maps.
Generate a client in any language that OpenAPI Generator supports, for example Go:
openapi-generator-cli generate -i ontology.json -g go -o ./converge-client
The Java package is built from this same document with OpenAPI Generator 7.14.0, so that version is known to work. Objects come back as generic records keyed by property name. For typed object classes, use the TypeScript, Python or Java typed classes.