Installation
Install the Garnet API SDK using Go modules:
go get github.com/garnet-org/api
The SDK requires Go 1.25.0 or later. Update your go.mod file accordingly.
Authentication
Before using the SDK, you’ll need an authentication token from the Garnet platform. There are three types of tokens available:
User Token
Agent Token
Project Token
User tokens are used for general API access. Get your token from the Garnet dashboard.import "github.com/garnet-org/api/client"
// Initialize with user token
c := client.New("", "your-user-token")
The client automatically uses the default API endpoint (https://api.garnet.ai) when the base URL is empty. Agent tokens are issued when you create an agent. Use them for agent-to-API communication.import "github.com/garnet-org/api/client"
// Initialize with agent token
c := client.New("", "").WithAgentToken("agent-token")
Project tokens provide project-scoped access for automation.import "github.com/garnet-org/api/client"
// Initialize with project token
c := client.New("", "").WithProjectToken("project-token")
For self-hosted Garnet instances, specify your custom base URL:c := client.New("https://garnet.yourcompany.com", "your-token")
Your First API Call
Let’s create a new security agent and retrieve it:
Initialize the Client
Create a new client instance with your project token:package main
import (
"context"
"fmt"
"log"
"github.com/garnet-org/api/client"
"github.com/garnet-org/api/types"
)
func main() {
// Initialize client with project token
c := client.New("", "").WithProjectToken("your-project-token")
ctx := context.Background()
}
Create an Agent
Register a new agent with the Garnet platform:// Define agent configuration
newAgent := types.CreateAgent{
Kind: types.AgentKindVanilla,
OS: "linux",
Arch: "amd64",
Hostname: "web-server-1",
Version: "1.0.0",
IP: "10.0.1.42",
MachineID: "srv-a1b2c3d4",
Labels: types.AgentLabels{
"environment": "production",
"region": "us-east-1",
"team": "platform",
},
VanillaContext: &types.AgentVanillaContext{
Environment: "production",
},
}
// Create the agent
created, err := c.CreateAgent(ctx, newAgent)
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
fmt.Printf("Agent created with ID: %s\n", created.ID)
fmt.Printf("Agent token: %s\n", created.AgentToken)
The AgentToken returned should be stored securely and used by your agent for subsequent API calls.
Retrieve the Agent
Fetch the agent details using its ID:// Retrieve agent by ID
agent, err := c.Agent(ctx, created.ID)
if err != nil {
log.Fatalf("Failed to retrieve agent: %v", err)
}
fmt.Printf("Agent: %s\n", agent.Hostname)
fmt.Printf("Status: Active=%v\n", agent.Active)
fmt.Printf("Last Seen: %s\n", agent.LastSeen)
fmt.Printf("Labels: %+v\n", agent.Labels)
Complete Example
Here’s a complete working example that creates an agent, lists all agents in a project, and sends a heartbeat:
package main
import (
"context"
"fmt"
"log"
"github.com/garnet-org/api/client"
"github.com/garnet-org/api/types"
)
func main() {
// Initialize client with project token for agent creation
projectClient := client.New("", "").WithProjectToken("your-project-token")
ctx := context.Background()
// Create a new agent
newAgent := types.CreateAgent{
Kind: types.AgentKindVanilla,
OS: "linux",
Arch: "amd64",
Hostname: "api-server-1",
Version: "1.0.0",
IP: "10.0.1.50",
MachineID: "srv-xyz789",
Labels: types.AgentLabels{
"environment": "staging",
"service": "api",
},
VanillaContext: &types.AgentVanillaContext{
Environment: "staging",
},
}
created, err := projectClient.CreateAgent(ctx, newAgent)
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
fmt.Printf("✓ Agent created: %s\n", created.ID)
// Switch to agent token for agent operations
agentClient := client.New("", "").WithAgentToken(created.AgentToken)
// Send heartbeat to mark agent as active
err = agentClient.AgentHeartbeat(ctx)
if err != nil {
log.Fatalf("Failed to send heartbeat: %v", err)
}
fmt.Println("✓ Heartbeat sent successfully")
// List all agents in the project (using project token)
projectID := "your-project-id"
first := uint(10)
agents, err := projectClient.Agents(ctx, types.ListAgents{
ProjectID: projectID,
PageArgs: types.CursorPageArgs{
First: &first,
},
})
if err != nil {
log.Fatalf("Failed to list agents: %v", err)
}
fmt.Printf("\n✓ Found %d agents:\n", agents.PageInfo.TotalCount)
for _, agent := range agents.Items {
fmt.Printf(" - %s (%s) - Active: %v\n",
agent.Hostname, agent.ID, agent.Active)
}
}
Never commit authentication tokens to version control. Use environment variables or a secure secret management system.
Filtering and Pagination
List agents with filters and pagination:
// Helper function to create pointer to uint
func ptr(i uint) *uint { return &i }
func ptrString(s string) *string { return &s }
// Filter by labels and environment
active := true
first := uint(50)
result, err := c.Agents(ctx, types.ListAgents{
ProjectID: "proj_abc123",
Active: &active, // Only active agents
OS: ptrString("linux"), // Linux only
Labels: types.AgentLabels{ // With specific labels
"environment": "production",
},
PageArgs: types.CursorPageArgs{
First: &first, // First 50 results
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total agents: %d\n", result.PageInfo.TotalCount)
for _, agent := range result.Items {
fmt.Printf("%s - %s/%s\n", agent.Hostname, agent.OS, agent.Arch)
}
// Fetch next page if available
if result.PageInfo.HasNextPage {
nextResult, err := c.Agents(ctx, types.ListAgents{
ProjectID: "proj_abc123",
PageArgs: types.CursorPageArgs{
First: &first,
After: result.PageInfo.EndCursor,
},
})
// Process next page...
_ = nextResult
_ = err
}
Working with Events
Ingest and query security events from your agents:
import "time"
// Ingest an event (using agent token)
agentClient := client.New("", "").WithAgentToken("agent-token")
event := types.CreateOrUpdateEventV2{
// Event data in ashkaal format
// See ashkaal documentation for event schema
}
result, err := agentClient.IngestEventV2(ctx, event)
if err != nil {
log.Fatalf("Failed to ingest event: %v", err)
}
fmt.Printf("Event ingested: %s\n", result.ID)
// Query events with filters
page := 1
perPage := 20
timeStart := time.Now().Add(-24 * time.Hour)
events, err := c.Events(ctx, types.ListEvents{
Filters: &types.EventFilters{
AgentID: ptrString("agent_123"),
TimeStart: &timeStart,
},
PageArgs: types.PageArgs{
Page: &page,
PerPage: &perPage,
},
})
if err != nil {
log.Fatal(err)
}
for _, evt := range events.Data {
fmt.Printf("Event: %s at %s\n", evt.ID, evt.CreatedAt)
}
Updating Agents
Update agent metadata and configuration:
// Update agent hostname and labels
newHostname := "web-server-1-updated"
update := types.UpdateAgent{
Hostname: &newHostname,
}
err := c.UpdateAgent(ctx, "agent_123", update)
if err != nil {
log.Fatalf("Failed to update agent: %v", err)
}
fmt.Println("Agent updated successfully")
Error Handling Best Practices
Always handle errors appropriately:
import "errors"
// Check for specific error types
agent, err := c.Agent(ctx, agentID)
if err != nil {
switch {
case errors.Is(err, types.ErrAgentNotFound):
fmt.Println("Agent not found")
return
case errors.Is(err, types.ErrUnauthorizedAgent):
fmt.Println("Permission denied")
return
default:
log.Fatalf("Unexpected error: %v", err)
}
}
fmt.Printf("Found agent: %s\n", agent.Hostname)
Enabling Debug Mode
Enable debug mode to see raw HTTP requests and responses:
c := client.New("", "your-token")
c.Debug = true
// All subsequent requests will print debug information
agent, err := c.Agent(ctx, "agent_123")
Debug mode prints the full HTTP request including headers and body. Avoid using it in production with sensitive data.
Next Steps
Agent Management
Learn advanced agent management techniques
Event Monitoring
Deep dive into event ingestion and querying
Network Policies
Implement security policies across your infrastructure
API Reference
Explore the complete API reference
Common Patterns
Agent Heartbeat Loop
Keep your agent marked as active with periodic heartbeats:
import "time"
func runHeartbeatLoop(ctx context.Context, c *client.Client) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := c.AgentHeartbeat(ctx); err != nil {
log.Printf("Heartbeat failed: %v", err)
}
case <-ctx.Done():
return
}
}
}
Paginating Through All Results
Iterate through all pages of results:
func getAllAgents(ctx context.Context, c *client.Client, projectID string) ([]types.Agent, error) {
var allAgents []types.Agent
first := uint(100)
var after *string
for {
result, err := c.Agents(ctx, types.ListAgents{
ProjectID: projectID,
PageArgs: types.CursorPageArgs{
First: &first,
After: after,
},
})
if err != nil {
return nil, err
}
allAgents = append(allAgents, result.Items...)
if !result.PageInfo.HasNextPage {
break
}
after = result.PageInfo.EndCursor
}
return allAgents, nil
}
For large datasets, consider processing pages incrementally rather than loading everything into memory.