Native Go API client for Palo Alto Networks Cortex XSOAR 8.x / XSIAM.
- Modern Go - Requires Go 1.24+, uses
iter.Seq2iterators for pagination - Type-safe - Strongly typed models with
errors.As()support for error handling - Service-based - Clean API surface:
client.Incidents.Search(),client.Incidents.Get() - Flexible - Functional options pattern for configuration
- Testable - Interfaces with mockery support, injectable HTTP client
go get github.com/tphakala/go-xsoarpackage main
import (
"context"
"fmt"
"log"
"github.com/tphakala/go-xsoar"
)
func main() {
// Create client
client, err := xsoar.NewClient(
xsoar.WithBaseURL("https://api-tenant.xdr.us.paloaltonetworks.com"),
xsoar.WithAPIKey("your-key-id", "your-api-key"),
)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// Search incidents using iterator
for incident, err := range client.Incidents.Search(ctx, &xsoar.IncidentFilter{
Status: []xsoar.IncidentStatus{xsoar.StatusActive},
}) {
if err != nil {
log.Fatal(err)
}
fmt.Printf("Incident: %s - %s\n", incident.ID, incident.Name)
}
}XSOAR 8.x / XSIAM uses advanced API key authentication with two headers:
| Header | Value |
|---|---|
x-xdr-auth-id |
API Key ID |
Authorization |
API Key |
Generate API keys in XSOAR/XSIAM under Settings > API Keys.
client, err := xsoar.NewClient(
xsoar.WithBaseURL("https://api-tenant.xdr.us.paloaltonetworks.com"),
xsoar.WithAPIKey(keyID, apiKey),
xsoar.WithTimeout(30 * time.Second), // optional
xsoar.WithHTTPClient(customClient), // optional
xsoar.WithUserAgent("my-app/1.0"), // optional
)// Using iterator (fetches pages lazily)
filter := &xsoar.IncidentFilter{
Status: []xsoar.IncidentStatus{xsoar.StatusActive},
Severity: []xsoar.Severity{xsoar.SeverityHigh, xsoar.SeverityCritical},
}
for incident, err := range client.Incidents.Search(ctx, filter) {
if err != nil {
return err
}
process(incident)
}
// Collect all results into a slice
incidents, err := xsoar.Collect(client.Incidents.Search(ctx, filter))
// Get first N results
incidents, err := xsoar.CollectN(client.Incidents.Search(ctx, filter), 10)
// Get first result only
incident, err := xsoar.First(client.Incidents.Search(ctx, filter))
// Low-level pagination control
page, err := client.Incidents.SearchPage(ctx, filter, &xsoar.PageOptions{
Offset: 0,
Limit: 100,
})// Get incident by ID
incident, err := client.Incidents.Get(ctx, "inc-123")
// Create incident
incident, err := client.Incidents.Create(ctx, &xsoar.CreateIncidentRequest{
Name: "Security Alert",
Type: "Malware",
Severity: xsoar.SeverityHigh,
CustomFields: map[string]any{
"source": "SIEM",
},
})
// Update incident
severity := xsoar.SeverityCritical
owner := "analyst@company.com"
err := client.Incidents.Update(ctx, "inc-123", &xsoar.UpdateIncidentRequest{
Severity: &severity,
Owner: &owner,
})
// Close incident
err := client.Incidents.Close(ctx, "inc-123", &xsoar.CloseIncidentRequest{
Reason: "Resolved",
Notes: "False positive confirmed",
})
// Delete incident
err := client.Incidents.Delete(ctx, "inc-123")incident, err := client.Incidents.Get(ctx, "inc-123",
xsoar.WithRequestID("trace-abc-123"),
xsoar.WithHeader("X-Custom-Header", "value"),
)All errors implement the standard error interface and can be inspected using errors.As():
incident, err := client.Incidents.Get(ctx, "inc-123")
if err != nil {
var authErr *xsoar.AuthenticationError
var notFoundErr *xsoar.NotFoundError
var validationErr *xsoar.ValidationError
var rateLimitErr *xsoar.RateLimitError
var serverErr *xsoar.ServerError
switch {
case errors.As(err, &authErr):
log.Fatal("Invalid credentials")
case errors.As(err, ¬FoundErr):
log.Printf("Incident %s not found", notFoundErr.ResourceID)
case errors.As(err, &validationErr):
log.Printf("Validation error: %s", validationErr.Message)
case errors.As(err, &rateLimitErr):
log.Printf("Rate limited, retry after %s", rateLimitErr.RetryAfter)
case errors.As(err, &serverErr):
log.Printf("Server error: %s", serverErr.Message)
default:
log.Printf("Error: %v", err)
}
}| Constant | Value | String |
|---|---|---|
SeverityUnknown |
0 | "Unknown" |
SeverityInfo |
1 | "Info" |
SeverityLow |
2 | "Low" |
SeverityMedium |
3 | "Medium" |
SeverityHigh |
4 | "High" |
SeverityCritical |
5 | "Critical" |
| Constant | Value |
|---|---|
StatusActive |
"Active" |
StatusPending |
"Pending" |
StatusDone |
"Done" |
StatusArchived |
"Archive" |
The package provides helper functions for working with iter.Seq2[T, error] iterators:
// Collect all items
items, err := xsoar.Collect(iterator)
// Collect up to N items
items, err := xsoar.CollectN(iterator, 10)
// Get first item (returns ErrEmptyIterator if empty)
item, err := xsoar.First(iterator)
// Take first N items (returns new iterator)
limited := xsoar.Take(iterator, 5)
// Filter items
filtered := xsoar.Filter(iterator, func(i *xsoar.Incident) bool {
return i.Severity >= xsoar.SeverityHigh
})
// Transform items
mapped := xsoar.Map(iterator, func(i *xsoar.Incident) string {
return i.ID
})The package provides interfaces that can be mocked using mockery:
go generate ./...Example test:
func TestMyService(t *testing.T) {
mockIncidents := mocks.NewIncidentService(t)
mockIncidents.On("Get", mock.Anything, "inc-123", mock.Anything).
Return(&xsoar.Incident{ID: "inc-123", Name: "Test"}, nil)
// Use mockIncidents in your tests
}- Go 1.24 or later
- XSOAR 8.x or XSIAM (legacy XSOAR versions not supported)
MIT