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:
User creates a project token via the API with specific permissions
Token is returned once and stored securely
Applications include token in X-Project-Token header
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
// 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" )
Project tokens are sent in the X-Project-Token header:
X-Project-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
The SDK automatically sets this header:
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:
Resource Permissions Agents agent:create, agent:read, agent:update, agent:delete, agent:listEvents event:read, event:listIssues issue:create, issue:read, issue:update, issue:delete, issue:listTokens token:create, token:read, token:update, token:delete, token:listNetwork Policies network_policy:create, network_policy:read, network_policy:update, network_policy:delete, network_policy:listProject Settings project_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.
// 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
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!
}
Use Environment Variables
Never hardcode project tokens: // ✅ GOOD
token := os . Getenv ( "GARNET_PROJECT_TOKEN" )
// ❌ BAD
token := "eyJhbGciOiJIUzI1NiIs..."
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
}
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 )
}
}
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
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