Skip to main content

Overview

Project tokens are API tokens designed for programmatic access to the Garnet API. They provide a secure way to authenticate automated workflows, CI/CD pipelines, and integrations without requiring user credentials. Project tokens support granular permission control, allowing you to grant only the specific permissions needed.

How It Works

Project tokens use header-based authentication:
  1. User creates a project token via the API with specific permissions
  2. Token is returned once and stored securely
  3. Applications include token in X-Project-Token header
  4. API validates token and enforces permission checks
Project tokens are created by authenticated users and inherit permission constraints from the creating user.

Creating Project Tokens

Prerequisites

You need a user token with appropriate permissions to create project tokens:
  • create permission on token resource
  • User can only grant permissions they themselves possess

Create Token Request

import (
    "context"
    "fmt"
    "github.com/garnet-org/api/client"
    "github.com/garnet-org/api/types"
)

func createProjectToken(userClient *client.Client) (string, error) {
    createReq := types.CreateToken{
        Name: "CI/CD Pipeline Token",
        Permissions: []types.Permission{
            types.PermissionAgentRead,
            types.PermissionAgentList,
            types.PermissionEventRead,
            types.PermissionEventList,
            types.PermissionIssueRead,
            types.PermissionIssueList,
        },
    }
    
    var resp types.TokenCreated
    err := userClient.Post(
        context.Background(),
        &resp,
        "/api/v1/tokens",
        createReq,
    )
    if err != nil {
        return "", fmt.Errorf("failed to create token: %w", err)
    }
    
    fmt.Printf("Token created: %s\n", resp.Name)
    fmt.Printf("Token ID: %s\n", resp.ID)
    
    // IMPORTANT: resp.Token is only returned once
    return resp.Token, nil
}
Critical: The token value is only returned once in the TokenCreated response. Store it securely immediately. If lost, you must create a new token.

Configuration

Configuring Client with Project Token

client/client.go
// WithProjectToken configures the client to use a project token for authentication.
func (c *Client) WithProjectToken(token string) *Client {
	client := c.Clone()
	client.AuthToken = token
	client.TokenType = TokenTypeProject
	return client
}
// Create client with project token
projectToken := os.Getenv("GARNET_PROJECT_TOKEN")
projectClient := client.New("", "").WithProjectToken(projectToken)

// Use for API requests
var agents []types.Agent
err := projectClient.Get(context.Background(), &agents, "/api/v1/agents")

HTTP Header Format

Project tokens are sent in the X-Project-Token header:
X-Project-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
The SDK automatically sets this header:
client/client.go
switch c.TokenType {
case TokenTypeProject:
    req.Header.Set("X-Project-Token", c.AuthToken)
}

Permission System

Available Permissions

Project tokens support granular permissions across all resource types:
ResourcePermissions
Agentsagent:create, agent:read, agent:update, agent:delete, agent:list
Eventsevent:read, event:list
Issuesissue:create, issue:read, issue:update, issue:delete, issue:list
Tokenstoken:create, token:read, token:update, token:delete, token:list
Network Policiesnetwork_policy:create, network_policy:read, network_policy:update, network_policy:delete, network_policy:list
Project Settingsproject_setting:create, project_setting:read, project_setting:update, project_setting:delete, project_setting:list

Permission Validation

The API validates permissions before allowing operations:
// This token only has 'read' permissions
projectClient := client.New("", "").WithProjectToken(readOnlyToken)

// ✅ Allowed: Reading agents
var agents []types.Agent
err := projectClient.Get(ctx, &agents, "/api/v1/agents")

// ❌ Forbidden: Creating agents (403 error)
err = projectClient.Post(ctx, &resp, "/api/v1/agents", createReq)
// Returns: 403 Forbidden - missing 'agent:create' permission

Empty Permissions

If you create a token with an empty permissions array, the token inherits all permissions that the creating user has.
types/token.go
// Validate ensures the CreateToken request is valid.
func (c *CreateToken) Validate() error {
    // Empty permissions list means all permissions will be granted
    // Let's validate if permissions are provided
    if len(c.Permissions) > 0 {
        for _, p := range c.Permissions {
            if !slices.Contains(AllPermissions(), p) {
                return ErrInvalidPermission
            }
        }
    }
    return nil
}

Managing Project Tokens

List Tokens

func listTokens(userClient *client.Client) error {
    var page types.Page
    err := userClient.Get(
        context.Background(),
        &page,
        "/api/v1/tokens?page=1&per_page=20",
    )
    if err != nil {
        return err
    }
    
    for _, token := range page.Data.([]types.Token) {
        fmt.Printf("Token: %s (ID: %s)\n", token.Name, token.ID)
        fmt.Printf("  Created: %s\n", token.CreatedAt)
        fmt.Printf("  Permissions: %v\n", token.Permissions)
        if token.LastUsed != nil {
            fmt.Printf("  Last Used: %s\n", *token.LastUsed)
        }
    }
    
    return nil
}

Get Token Details

func getToken(userClient *client.Client, tokenID string) (*types.Token, error) {
    var token types.Token
    err := userClient.Get(
        context.Background(),
        &token,
        fmt.Sprintf("/api/v1/tokens/%s", tokenID),
    )
    return &token, err
}

Update Token

Update token name or permissions:
func updateToken(userClient *client.Client, tokenID string) error {
    newName := "Updated Token Name"
    updateReq := types.UpdateToken{
        Name: &newName,
        Permissions: []types.Permission{
            types.PermissionAgentRead,
            types.PermissionAgentList,
            types.PermissionIssueRead,
            types.PermissionIssueList,
        },
    }
    
    var resp types.TokenUpdated
    err := userClient.Patch(
        context.Background(),
        &resp,
        fmt.Sprintf("/api/v1/tokens/%s", tokenID),
        updateReq,
    )
    return err
}
You can only update tokens with permissions you currently possess. Attempting to grant additional permissions you don’t have results in 403 Forbidden.

Delete Token

Revoke a token immediately:
func deleteToken(userClient *client.Client, tokenID string) error {
    return userClient.Delete(
        context.Background(),
        fmt.Sprintf("/api/v1/tokens/%s", tokenID),
    )
}
Deleting a token immediately revokes access. All subsequent requests with that token return 401 Unauthorized.

Use Cases

CI/CD Pipelines

Create read-only tokens for monitoring in CI/CD:
// Read-only token for CI checks
token := types.CreateToken{
    Name: "GitHub Actions - Read Only",
    Permissions: []types.Permission{
        types.PermissionAgentRead,
        types.PermissionAgentList,
        types.PermissionEventRead,
        types.PermissionEventList,
        types.PermissionIssueRead,
        types.PermissionIssueList,
    },
}

Automation Scripts

Create tokens with specific permissions for automation:
// Token for automated issue management
token := types.CreateToken{
    Name: "Issue Automation Bot",
    Permissions: []types.Permission{
        types.PermissionIssueRead,
        types.PermissionIssueUpdate,
        types.PermissionIssueList,
        types.PermissionEventRead,
        types.PermissionEventList,
    },
}

Third-Party Integrations

Create limited tokens for external integrations:
// Token for Slack integration
token := types.CreateToken{
    Name: "Slack Notification Service",
    Permissions: []types.Permission{
        types.PermissionIssueRead,
        types.PermissionIssueList,
        types.PermissionAgentRead,
        types.PermissionAgentList,
    },
}

Security Best Practices

1

Apply Least Privilege

Only grant permissions that are absolutely necessary:
// ✅ GOOD: Minimal permissions
token := types.CreateToken{
    Name: "Read-Only Monitor",
    Permissions: []types.Permission{
        types.PermissionAgentRead,
        types.PermissionAgentList,
    },
}

// ❌ BAD: Unnecessary permissions
token := types.CreateToken{
    Name: "Read-Only Monitor",
    Permissions: []types.Permission{}, // Grants ALL permissions!
}
2

Use Environment Variables

Never hardcode project tokens:
// ✅ GOOD
token := os.Getenv("GARNET_PROJECT_TOKEN")

// ❌ BAD
token := "eyJhbGciOiJIUzI1NiIs..."
3

Rotate Tokens Regularly

Implement token rotation:
func rotateToken(client *client.Client, oldTokenID string) (string, error) {
    // Create new token with same permissions
    newToken, err := createProjectToken(client)
    if err != nil {
        return "", err
    }
    
    // Delete old token
    if err := deleteToken(client, oldTokenID); err != nil {
        log.Printf("Warning: failed to delete old token: %v", err)
    }
    
    return newToken, nil
}
4

Monitor Token Usage

Track token usage with the last_used field:
token, _ := getToken(client, tokenID)
if token.LastUsed != nil {
    if time.Since(*token.LastUsed) > 30*24*time.Hour {
        log.Printf("Token %s unused for 30+ days - consider deletion", token.Name)
    }
}
5

Descriptive Token Names

Use clear, descriptive names:
// ✅ GOOD: Clear purpose
Name: "GitHub Actions CI - Production Monitoring"

// ❌ BAD: Unclear purpose
Name: "token1"

Error Handling

Common Errors

err := projectClient.Get(ctx, &result, "/api/v1/agents")
if err != nil {
    if strings.Contains(err.Error(), "401") {
        // Token is invalid, expired, or revoked
        return fmt.Errorf("authentication failed: %w", err)
    }
    if strings.Contains(err.Error(), "403") {
        // Token lacks required permissions
        return fmt.Errorf("permission denied: token lacks required permissions: %w", err)
    }
}

Token Creation Errors

types/token.go
const (
	ErrInvalidTokenName        = errs.InvalidArgumentError("invalid token name")
	ErrTokenNameTooLong        = errs.InvalidArgumentError("token name exceeds maximum length")
	ErrTokenNotFound           = errs.NotFoundError("token not found")
	ErrTokenExists             = errs.ConflictError("token already exists")
	ErrUnauthorizedTokenAccess = errs.UnauthorizedError("permission denied for token access")
)
err := userClient.Post(ctx, &resp, "/api/v1/tokens", createReq)
if err != nil {
    if strings.Contains(err.Error(), "invalid token name") {
        return fmt.Errorf("token name is required: %w", err)
    }
    if strings.Contains(err.Error(), "exceeds maximum length") {
        return fmt.Errorf("token name too long (max 64 chars): %w", err)
    }
    if strings.Contains(err.Error(), "403") {
        return fmt.Errorf("cannot create token with permissions you don't have: %w", err)
    }
}

Complete Example

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    
    "github.com/garnet-org/api/client"
    "github.com/garnet-org/api/types"
)

func main() {
    // Step 1: Create project token with user credentials
    userToken := os.Getenv("GARNET_USER_TOKEN")
    userClient := client.New("https://api.garnet.ai", userToken)
    
    projectToken, err := createProjectToken(userClient)
    if err != nil {
        log.Fatalf("Failed to create project token: %v", err)
    }
    
    fmt.Printf("Project token created. Store securely: %s\n", projectToken)
    
    // Step 2: Use project token for API access
    projectClient := userClient.WithProjectToken(projectToken)
    
    // Step 3: Make API requests
    var agents []types.Agent
    err = projectClient.Get(context.Background(), &agents, "/api/v1/agents")
    if err != nil {
        log.Fatalf("Failed to list agents: %v", err)
    }
    
    fmt.Printf("Found %d agents\n", len(agents))
}

func createProjectToken(client *client.Client) (string, error) {
    createReq := types.CreateToken{
        Name: "Automation Token",
        Permissions: []types.Permission{
            types.PermissionAgentRead,
            types.PermissionAgentList,
            types.PermissionEventRead,
            types.PermissionEventList,
        },
    }
    
    var resp types.TokenCreated
    err := client.Post(context.Background(), &resp, "/api/v1/tokens", createReq)
    if err != nil {
        return "", err
    }
    
    return resp.Token, nil
}

Next Steps

User Tokens

Learn about user authentication

Agent Tokens

Configure agent authentication

Build docs developers (and LLMs) love