Skip to main content

API Integration

This document covers how to integrate with OctoMesh APIs, including authentication, GraphQL operations, and the service client SDK.

API Overview

OctoMesh provides multiple API access methods:

API TypeProtocolUse Case
GraphQLHTTPSPrimary data access, queries, mutations
RESTHTTPSAdministrative operations, file uploads
AMQPRabbitMQReal-time messaging, adapter communication

Authentication

OAuth 2.0 / OpenID Connect

OctoMesh uses OAuth 2.0 with OpenID Connect for authentication.

Obtaining Access Token

Token Request:

POST /connect/token HTTP/1.1
Host: identity.octomesh.local
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=my-application
&client_secret=my-secret
&scope=octomesh.read octomesh.write

Token Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "octomesh.read octomesh.write"
}

API Scopes

ScopeDescription
octomesh.readRead access to runtime and stream data
octomesh.writeWrite access to runtime data
octomesh.adminAdministrative operations
octomesh.ck.readRead Construction Kit definitions
octomesh.ck.writeModify Construction Kit definitions

Using the Token

Include the token in the Authorization header:

GET /tenants/my-tenant/graphql HTTP/1.1
Host: asset-repo.octomesh.local
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

GraphQL API

Endpoint

https://{asset-repo-host}/tenants/{tenantId}/graphql

GraphQL Playground (interactive explorer):

https://{asset-repo-host}/tenants/{tenantId}/graphql/playground

Query Structure

The GraphQL schema is organized into two main query roots:

type Query {
runtime: RuntimeQuery # Master data (MongoDB)
streamData: StreamQuery # Time-series data (CrateDB)
}

type Mutation {
runtime: RuntimeMutation
}

Runtime Queries

Query all entities of a type:

query GetEnergyMeters {
runtime {
rtIndustryEnergyEnergyMeter(
first: 50
after: "cursor"
filter: {
status: { eq: "Active" }
}
orderBy: { name: ASC }
) {
items {
rtId
ckTypeId
wellKnownName
name
serialNumber
status
location {
items {
rtId
name
address
}
}
}
pageInfo {
hasNextPage
endCursor
totalCount
}
}
}
}

Query by ID:

query GetMeterById($rtId: OctoObjectId!) {
runtime {
rtIndustryEnergyEnergyMeterById(rtId: $rtId) {
rtId
name
serialNumber
readings(first: 10) {
items {
timestamp
value
}
}
}
}
}

Query by wellKnownName:

query GetMeterByName {
runtime {
rtIndustryEnergyEnergyMeterByWellKnownName(
wellKnownName: "main-building-meter"
) {
rtId
name
}
}
}

Stream Data Queries

Query time-series data:

query GetMeterReadings($rtId: OctoObjectId!) {
streamData {
tsIndustryEnergyMeterReading(
first: 1000
filter: {
rtId: { eq: $rtId }
timestamp: {
gte: "2024-01-01T00:00:00Z"
lte: "2024-01-31T23:59:59Z"
}
}
orderBy: { timestamp: DESC }
) {
items {
rtId
timestamp
voltage
current
power
energy
}
pageInfo {
hasNextPage
endCursor
}
}
}
}

Mutations

Create entity:

mutation CreateEnergyMeter {
runtime {
createRtIndustryEnergyEnergyMeter(
entity: {
wellKnownName: "new-meter-001"
attributes: {
name: "New Energy Meter"
serialNumber: "EM-2024-001"
manufacturer: "Siemens"
status: Active
}
}
) {
rtId
wellKnownName
}
}
}

Update entity:

mutation UpdateEnergyMeter($rtId: OctoObjectId!) {
runtime {
updateRtIndustryEnergyEnergyMeter(
rtId: $rtId
entity: {
attributes: {
status: Maintenance
}
}
) {
rtId
status
}
}
}

Delete entity:

mutation DeleteEnergyMeter($rtId: OctoObjectId!) {
runtime {
deleteRtIndustryEnergyEnergyMeter(rtId: $rtId)
}
}

Manage associations:

mutation AddMeterToLocation($meterId: OctoObjectId!, $locationId: OctoObjectId!) {
runtime {
addAssociationRtIndustryEnergyEnergyMeter(
rtId: $meterId
association: "location"
targetRtIds: [$locationId]
) {
rtId
location {
items {
rtId
}
}
}
}
}

Filtering

OctoMesh GraphQL supports rich filtering:

query FilteredQuery {
runtime {
rtIndustryEnergyEnergyMeter(
filter: {
AND: [
{ status: { eq: "Active" } }
{
OR: [
{ manufacturer: { contains: "Siemens" } }
{ manufacturer: { contains: "ABB" } }
]
}
{ maxCapacity: { gte: 100 } }
]
}
) {
items {
rtId
name
}
}
}
}

Available filter operators:

OperatorDescription
eqEquals
neqNot equals
gt / gteGreater than / Greater than or equal
lt / lteLess than / Less than or equal
containsString contains
startsWithString starts with
endsWithString ends with
inValue in list
AND / ORLogical operators

Service Client SDK

The .NET SDK provides a typed client for OctoMesh APIs.

Installation

dotnet add package Meshmakers.Octo.Sdk.ServiceClient

Configuration

using Meshmakers.Octo.Sdk.ServiceClient;

var services = new ServiceCollection();

services.AddOctoMeshServiceClient(options =>
{
options.IdentityServiceUrl = "https://identity.octomesh.local";
options.AssetRepositoryUrl = "https://asset-repo.octomesh.local";
options.ClientId = "my-application";
options.ClientSecret = "my-secret";
options.TenantId = "my-tenant";
});

Using the Service Client

public class MyService
{
private readonly IAssetRepositoryClient _client;

public MyService(IAssetRepositoryClient client)
{
_client = client;
}

public async Task<IEnumerable<EnergyMeter>> GetActiveMetersAsync(
CancellationToken cancellationToken)
{
var query = new GraphQLRequest
{
Query = @"
query {
runtime {
rtIndustryEnergyEnergyMeter(
filter: { status: { eq: ""Active"" } }
) {
items {
rtId
name
serialNumber
}
}
}
}"
};

var response = await _client.ExecuteGraphQLAsync<EnergyMeterResponse>(
query, cancellationToken);

return response.Runtime.RtIndustryEnergyEnergyMeter.Items;
}

public async Task<string> CreateMeterAsync(
CreateMeterRequest request,
CancellationToken cancellationToken)
{
var mutation = new GraphQLRequest
{
Query = @"
mutation CreateMeter($input: CreateEnergyMeterInput!) {
runtime {
createRtIndustryEnergyEnergyMeter(entity: $input) {
rtId
}
}
}",
Variables = new { input = request }
};

var response = await _client.ExecuteGraphQLAsync<CreateMeterResponse>(
mutation, cancellationToken);

return response.Runtime.CreateRtIndustryEnergyEnergyMeter.RtId;
}
}

Source Generation

Use source generation to create typed DTOs:

dotnet add package Meshmakers.Octo.Sdk.SourceGeneration
// Define your models with source generation attributes
[GenerateGraphQLTypes("Industry.Energy/EnergyMeter")]
public partial class EnergyMeterDto
{
}

// Generated code provides:
// - Strongly typed properties matching CK attributes
// - Serialization support
// - GraphQL query/mutation helpers

REST API

Administrative Endpoints

Get tenant information:

GET /api/tenants/{tenantId}
Authorization: Bearer {token}

Upload file:

POST /api/tenants/{tenantId}/files
Authorization: Bearer {token}
Content-Type: multipart/form-data

file=@document.pdf

Get system health:

GET /health

Construction Kit Management

List CK libraries:

GET /api/tenants/{tenantId}/construction-kits
Authorization: Bearer {token}

Upload CK library:

POST /api/tenants/{tenantId}/construction-kits
Authorization: Bearer {token}
Content-Type: application/json

{
"libraryId": "MyCompany.Domain",
"version": "1.0.0",
"types": [...],
"enums": [...]
}

Error Handling

GraphQL Errors

GraphQL errors are returned in the errors array:

{
"data": null,
"errors": [
{
"message": "Entity not found",
"locations": [{ "line": 2, "column": 3 }],
"path": ["runtime", "rtIndustryEnergyEnergyMeterById"],
"extensions": {
"code": "ENTITY_NOT_FOUND",
"details": {
"rtId": "invalid-id"
}
}
}
]
}

Common Error Codes

CodeDescription
UNAUTHORIZEDMissing or invalid token
FORBIDDENInsufficient permissions
ENTITY_NOT_FOUNDRequested entity does not exist
VALIDATION_ERRORInput validation failed
CK_TYPE_NOT_FOUNDConstruction Kit type not found
ASSOCIATION_ERRORInvalid association operation

Client-Side Error Handling

try
{
var result = await _client.ExecuteGraphQLAsync<Response>(query, ct);

if (result.Errors?.Any() == true)
{
foreach (var error in result.Errors)
{
_logger.LogError("GraphQL error: {Message} (Code: {Code})",
error.Message,
error.Extensions?["code"]);
}
throw new OctoMeshException("GraphQL operation failed", result.Errors);
}

return result.Data;
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "HTTP request failed");
throw;
}
catch (AuthenticationException ex)
{
_logger.LogError(ex, "Authentication failed");
// Refresh token and retry
throw;
}

Rate Limiting

OctoMesh implements rate limiting to protect the platform:

Endpoint TypeLimit
GraphQL queries100 req/min per client
Mutations50 req/min per client
File uploads10 req/min per client

When rate limited, you'll receive:

HTTP/1.1 429 Too Many Requests
Retry-After: 60

Best Practices

Query Optimization

  1. Request only needed fields: Reduce response size
  2. Use pagination: Always set first parameter
  3. Filter server-side: Don't fetch and filter client-side
  4. Batch related queries: Combine in single request

Connection Management

  1. Reuse HTTP clients: Use connection pooling
  2. Handle token refresh: Implement automatic refresh
  3. Implement retry logic: Handle transient failures
  4. Use timeouts: Set appropriate request timeouts

Security

  1. Secure credentials: Use secrets management
  2. Minimal scopes: Request only needed permissions
  3. Validate responses: Don't trust external data
  4. Log securely: Don't log tokens or sensitive data

Next Steps