# APIs and SDKs

> Cordium documentation. Canonical page: <https://octelium.com/docs/cordium/latest/use/api>.

The complete Cordium API is exposed over gRPC. This is the interface used by the `cordium` CLI as well as by the Cordium web portal. Cordium currently supports APIs and SDKs for Golang, Typescript/Javascript (see [@octelium/sdk](https://www.npmjs.com/package/@octelium/sdk) and @[@octelium/apis](https://www.npmjs.com/package/@octelium/apis)) and Python (see [octelium-apis](https://pypi.org/project/octelium-apis/) and [octelium-sdk](https://pypi.org/project/octelium-sdk/)).

Cordium resource JSON Schemas are published under `/schemas/cordium/latest/`, with filenames such as `cordium.v1.Workspace.schema.json`. Use them to look up exact fields, types, and enum values, for example the [Workspace JSON Schema](https://octelium.com/schemas/cordium/latest/cordium.v1.Workspace.schema.json) and [Template JSON Schema](https://octelium.com/schemas/cordium/latest/cordium.v1.Template.schema.json). The [resource concepts](https://octelium.com/docs/cordium/latest/overview/concepts.md) and [Cluster configuration](https://octelium.com/docs/cordium/latest/use/clusterconfig.md) pages include interactive schema viewers.

Here is a rather detailed example in Golang:

```go
package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"time"

	"github.com/octelium/octelium/apis/main/cordiumv1"
	"github.com/octelium/octelium/octelium-go"
	"github.com/octelium/octelium/pkg/apiutils/umetav1"
	"github.com/octelium/octelium/pkg/grpcerr"
)

func main() {
	if err := doMain(context.Background()); err != nil {
		panic(err)
	}
}

func doMain(ctx context.Context) error {
	octeliumC, err := octelium.NewClient(ctx, &octelium.ClientConfig{
		Domain:              "example.com",
		AuthenticationToken: "AUTH_TOKEN",
	})
	if err != nil {
		return err
	}

	defer octeliumC.Close()

	grpcConn, err := octeliumC.GRPC().GetConn(ctx)
	if err != nil {
		return err
	}
	c := cordiumv1.NewMainServiceClient(grpcConn)

	workspaceC := cordiumv1.NewWorkspaceServiceClient(grpcConn)

	{
		spaceList, err := c.ListSpace(ctx, &cordiumv1.ListSpaceOptions{})
		if err != nil {
			return err
		}

		for _, itm := range spaceList.Items {
			fmt.Printf("Space: %s\n", itm.Metadata.Name)
		}
	}

	{
		workspaceList, err := c.ListWorkspace(ctx, &cordiumv1.ListWorkspaceOptions{})
		if err != nil {
			return err
		}

		for _, itm := range workspaceList.Items {
			fmt.Printf("Workspace: %s\n", itm.Metadata.Name)
		}
	}
	{
		ws, err := c.CreateWorkspace(ctx, &cordiumv1.Workspace{
			Spec: &cordiumv1.Workspace_Spec{
				Image: &cordiumv1.Workspace_Spec_Image{
					Type: &cordiumv1.Workspace_Spec_Image_Registry_{
						Registry: &cordiumv1.Workspace_Spec_Image_Registry{
							Url: "node:22-bookworm",
						},
					},
				},

				Repository: &cordiumv1.Workspace_Spec_Repository{
					Url: "https://github.com/myorg/orders-api",
				},

				Runtime: &cordiumv1.Workspace_Spec_Runtime{
					EnvVars: []*cordiumv1.Workspace_Spec_Runtime_EnvVar{
						{
							Key: "NODE_ENV",
							Type: &cordiumv1.Workspace_Spec_Runtime_EnvVar_Value{
								Value: "development",
							},
						},
					},

					Tasks: []*cordiumv1.Workspace_Spec_Runtime_Task{
						{
							Name: "install",
							Type: cordiumv1.Workspace_Spec_Runtime_Task_ON_CREATE,
							Run: `
npm install
`,
							WorkingDir: "/workspace/repo",
						},

						{
							Name: "start-api",
							Type: cordiumv1.Workspace_Spec_Runtime_Task_POST_START,
							Run: `
npm run dev
`,
							WorkingDir:   "/workspace/repo",
							IsBackground: true,
						},
					},
				},

				Limit: &cordiumv1.Workspace_Spec_Limit{
					Cpu: &cordiumv1.Workspace_Spec_Limit_CPU{
						Millicores: 4000,
					},
					Memory: &cordiumv1.Workspace_Spec_Limit_Memory{
						Megabytes: 8192,
					},
				},
			},
		})
		if err != nil {
			return err
		}

		ws.Spec.Limit.Storage = &cordiumv1.Workspace_Spec_Limit_Storage{
			Megabytes: 20000,
		}

		ws, err = c.UpdateWorkspace(ctx, ws)
		if err != nil {
			return err
		}

		_, err = c.StartWorkspace(ctx, &cordiumv1.StartWorkspaceRequest{
			WorkspaceRef: umetav1.GetObjectReference(ws),
		})
		if err != nil {
			return err
		}

		watcher, err := c.WatchWorkspace(ctx, &cordiumv1.WatchWorkspaceRequest{
			WorkspaceRef: umetav1.GetObjectReference(ws),
		})
		if err != nil {
			return err
		}

		if err := func() error {
			for {
				msg, err := watcher.Recv()
				if err != nil {
					return err
				}

				switch msg.Type.(type) {
				case *cordiumv1.WatchWorkspaceResponse_Update_:
					cur := msg.GetUpdate().NewItem
					old := msg.GetUpdate().OldItem

					if cur.Status.State == old.Status.State {
						continue
					}

					switch cur.Status.State {
					case cordiumv1.Workspace_Status_PREPARING, cordiumv1.Workspace_Status_RUNNING:
						return nil
					}
				}
			}
		}(); err != nil {
			return err
		}

		execStream, err := workspaceC.Exec(ctx)
		if err != nil {
			return err
		}

		if err := execStream.Send(&cordiumv1.ExecRequest{
			Type: &cordiumv1.ExecRequest_Request_{
				Request: &cordiumv1.ExecRequest_Request{
					WorkspaceRef: umetav1.GetObjectReference(ws),
					Command:      "ls -la",
					WorkingDir:   "/",
				},
			},
		}); err != nil {
			return err
		}

		if err := func() error {
			for {
				select {
				case <-ctx.Done():

					return nil
				default:
					msg, err := execStream.Recv()
					if err != nil {
						if errors.Is(err, io.EOF) || grpcerr.IsCanceled(err) {
							return nil
						}
						time.Sleep(200 * time.Millisecond)
						continue
					}

					switch msg.Type.(type) {
					case *cordiumv1.ExecResponse_Exit_:
						if msg.GetExit().Code != 0 {
							os.Exit(int(msg.GetExit().Code))
						}
						return nil
					case *cordiumv1.ExecResponse_Stdout_:
						if _, err := os.Stdout.Write(msg.GetStdout().Data); err != nil {
						} else {
							os.Stdout.Write([]byte("\n"))
						}
					case *cordiumv1.ExecResponse_Stderr_:
						if _, err := os.Stderr.Write(msg.GetStderr().Data); err != nil {
						} else {
							os.Stderr.Write([]byte("\n"))
						}
					}
				}
			}

			return nil

		}(); err != nil {
			return err
		}

		_, err = c.StopWorkspace(ctx, &cordiumv1.StopWorkspaceRequest{
			WorkspaceRef: umetav1.GetObjectReference(ws),
		})
		if err != nil {
			return err
		}
	}

	return nil
}
```

Here is the same example written in Python:

```python
import asyncio
import sys
from typing import AsyncIterator

import betterproto
from octelium.api.main.cordium.v1 import (
    ExecRequest,
    ExecRequestRequest,
    ListSpaceOptions,
    ListWorkspaceOptions,
    MainServiceStub,
    StartWorkspaceRequest,
    StopWorkspaceRequest,
    WatchWorkspaceRequest,
    Workspace,
    WorkspaceServiceStub,
    WorkspaceSpec,
    WorkspaceSpecImage,
    WorkspaceSpecImageRegistry,
    WorkspaceSpecLimit,
    WorkspaceSpecLimitCpu,
    WorkspaceSpecLimitMemory,
    WorkspaceSpecLimitStorage,
    WorkspaceSpecRepository,
    WorkspaceSpecRuntime,
    WorkspaceSpecRuntimeEnvVar,
    WorkspaceSpecRuntimeTask,
    WorkspaceSpecRuntimeTaskType,
    WorkspaceStatusState,
)
from octelium.api.main.meta.v1 import Metadata, ObjectReference
from octelium.sdk import AuthConfig, AuthTokenConfig, OcteliumClient, OcteliumClientConfig


def get_object_reference(obj: Workspace) -> ObjectReference:
    return ObjectReference(
        name=obj.metadata.name,
        uid=obj.metadata.uid,
    )


async def exec_requests(ws: Workspace) -> AsyncIterator[ExecRequest]:
    yield ExecRequest(
        request=ExecRequestRequest(
            workspace_ref=get_object_reference(ws),
            command="ls -la",
            working_dir="/",
        )
    )


async def main() -> None:
    async with await OcteliumClient.create(
        OcteliumClientConfig(
            domain="example.com",
            auth=AuthConfig(
                type="auth_token",
                auth_token=AuthTokenConfig(
                    token="AUTH_TOKEN"
                ),
            ),
        )
    ) as client:
        c = MainServiceStub(client._channel)
        workspace_c = WorkspaceServiceStub(client._channel)

        space_list = await c.list_space(ListSpaceOptions())
        for itm in space_list.items:
            print(f"Space: {itm.metadata.name}")

        workspace_list = await c.list_workspace(ListWorkspaceOptions())
        for itm in workspace_list.items:
            print(f"Workspace: {itm.metadata.name}")

        ws = await c.create_workspace(
            Workspace(
                metadata=Metadata(
                    name="my-workspace",
                ),
                spec=WorkspaceSpec(
                    image=WorkspaceSpecImage(
                        registry=WorkspaceSpecImageRegistry(
                            url="node:22-bookworm",
                        ),
                    ),
                    repository=WorkspaceSpecRepository(
                        url="https://github.com/myorg/orders-api",
                    ),
                    runtime=WorkspaceSpecRuntime(
                        env_vars=[
                            WorkspaceSpecRuntimeEnvVar(
                                key="NODE_ENV",
                                value="development",
                            ),
                        ],
                        tasks=[
                            WorkspaceSpecRuntimeTask(
                                name="install",
                                type=WorkspaceSpecRuntimeTaskType.ON_CREATE,
                                run="npm install\n",
                                working_dir="/workspace/repo",
                            ),
                            WorkspaceSpecRuntimeTask(
                                name="start-api",
                                type=WorkspaceSpecRuntimeTaskType.POST_START,
                                run="npm run dev\n",
                                working_dir="/workspace/repo",
                                is_background=True,
                            ),
                        ],
                    ),
                    limit=WorkspaceSpecLimit(
                        cpu=WorkspaceSpecLimitCpu(millicores=4000),
                        memory=WorkspaceSpecLimitMemory(megabytes=8192),
                    ),
                ),
            )
        )

        ws.spec.limit.storage = WorkspaceSpecLimitStorage(megabytes=20000)
        ws = await c.update_workspace(ws)

        await c.start_workspace(
            StartWorkspaceRequest(workspace_ref=get_object_reference(ws))
        )

        async for msg in c.watch_workspace(
            WatchWorkspaceRequest(workspace_ref=get_object_reference(ws))
        ):
            field_name, _ = betterproto.which_one_of(msg, "type")
            if field_name != "update":
                continue

            cur = msg.update.new_item
            old = msg.update.old_item

            if cur.status.state == old.status.state:
                continue

            if cur.status.state in (
                WorkspaceStatusState.PREPARING,
                WorkspaceStatusState.RUNNING,
            ):
                break

        async for msg in workspace_c.exec(exec_requests(ws)):
            field_name, _ = betterproto.which_one_of(msg, "type")
            if field_name == "exit":
                if msg.exit.code != 0:
                    sys.exit(msg.exit.code)
                break
            elif field_name == "stdout":
                sys.stdout.buffer.write(msg.stdout.data)
                sys.stdout.buffer.write(b"\n")
                sys.stdout.buffer.flush()
            elif field_name == "stderr":
                sys.stderr.buffer.write(msg.stderr.data)
                sys.stderr.buffer.write(b"\n")
                sys.stderr.buffer.flush()

        await c.stop_workspace(
            StopWorkspaceRequest(workspace_ref=get_object_reference(ws))
        )


if __name__ == "__main__":
    asyncio.run(main())
```
