Documentation
¶
Index ¶
- Constants
- Variables
- func IsAuthError(err error) bool
- func IsNotFoundError(err error) bool
- func NormalizeReceiver(receiver string) string
- func RedactJSONForLog(raw []byte) []byte
- type APIError
- type APIKeyInfo
- type CampaignContact
- type CampaignEstimateResponse
- type CampaignResponse
- type CaptureConflictError
- type Client
- func (client *Client) BaseURL() string
- func (client *Client) CreateAPIKey(mode, name string) (*GeneratedAPIKey, error)
- func (client *Client) CreateCampaign(request CreateCampaignRequest, idempotencyKey string) (*CampaignResponse, error)
- func (client *Client) CreateWebhookListener(req CreateWebhookListenerRequest) (*CreateWebhookListenerResponse, error)
- func (client *Client) Delete(path string) error
- func (client *Client) DeleteTemplate(templateID string) error
- func (client *Client) Do(method string, path string, body any, result any) error
- func (client *Client) DoWithHeaders(method string, path string, body any, result any, headers map[string]string) error
- func (client *Client) EstimateCampaign(templateName string, recipientCount int) (*CampaignEstimateResponse, error)
- func (client *Client) Get(path string, result any) error
- func (client *Client) GetCampaign(campaignID string) (*CampaignResponse, error)
- func (client *Client) GetContact(phone string) (*ContactResponse, error)
- func (client *Client) GetContactStats() (map[string]int64, error)
- func (client *Client) GetMessageStatus(messageID string) (*MessageResponse, error)
- func (client *Client) GetMessages(mode string, page int, size int) (map[string]any, error)
- func (client *Client) GetMetrics(mode string) (map[string]any, error)
- func (client *Client) GetTemplateStatus(templateID string) (*TemplateStatusResponse, error)
- func (client *Client) GetWalletBalance(mode string) (map[string]any, error)
- func (client *Client) HeartbeatWebhookListener(listenerID string) error
- func (client *Client) ImportContacts(contacts []ContactRequest) (string, error)
- func (client *Client) IsLiveMode() bool
- func (client *Client) ListAPIKeys() ([]APIKeyInfo, error)
- func (client *Client) ListCampaigns() ([]CampaignResponse, error)
- func (client *Client) ListContacts(query string, page int, size int) (*ContactsListResponse, error)
- func (client *Client) ListNumbers() ([]PhoneNumberResponse, error)
- func (client *Client) ListOrganizations() ([]Organization, error)
- func (client *Client) ListTemplates() ([]Template, error)
- func (client *Client) Mode() string
- func (client *Client) PollDeviceToken(options PollOptions) (*TokenResponse, error)
- func (client *Client) Post(path string, body any, result any) error
- func (client *Client) RefreshDeviceToken(clientID, refreshToken string) (*TokenResponse, error)
- func (client *Client) ReleaseWebhookListener(listenerID string) error
- func (client *Client) RequestDeviceCode(clientID, scope string) (*DeviceCodeResponse, error)
- func (client *Client) RevokeAPIKey(keyID string) error
- func (client *Client) SendMessage(request SendMessageRequest) (*MessageResponse, error)
- func (client *Client) SetVerbose(enabled bool)
- func (client *Client) StreamEvents(ctx context.Context) (<-chan SSEEvent, error)
- type ContactRequest
- type ContactResponse
- type ContactsListResponse
- type CreateCampaignRequest
- type CreateWebhookListenerRequest
- type CreateWebhookListenerResponse
- type DeviceCodeRequest
- type DeviceCodeResponse
- type DeviceTokenRequest
- type DryRunPreview
- type GeneratedAPIKey
- type MessageResponse
- type Organization
- type PhoneNumberResponse
- type PollOptions
- type RefreshTokenRequest
- type SSEEvent
- type SendMessageRequest
- type Template
- type TemplateStatusResponse
- type TokenResponse
Constants ¶
const ( DefaultTimeout = 10 * time.Second MaxRetries = 3 KeyPrefixLive = "ara_live_" KeyPrefixTest = "ara_test_" // DefaultMCPDryRunURL and MCPDryRunKey produce a Client that can be // constructed without credentials, suitable only for inspecting tool // registration via `arara mcp --dry-run`. The key is deliberately invalid // so it cannot be used against a live API. DefaultMCPDryRunURL = "https://invalid.local" MCPDryRunKey = "ara_test_DRY_RUN_INSPECT_ONLY" )
Variables ¶
var ErrCaptureNotSupported = errors.New("backend does not support webhook capture listeners (RFC 0003)")
ErrCaptureNotSupported is returned when the backend doesn't yet expose the capture-listener endpoints. The CLI uses it to fall back to passive SSE mode without breaking the user flow.
var ErrOAuthNotSupported = errors.New("OAuth device flow endpoint not found on this backend (older deploy?). Run 'arara login --key <api-key>' as fallback")
Functions ¶
func IsAuthError ¶
func IsNotFoundError ¶
func NormalizeReceiver ¶
func RedactJSONForLog ¶
RedactJSONForLog returns a copy of raw with sensitive field values replaced by "<REDACTED>". When raw is not valid JSON it returns a placeholder noting the byte length, so opaque token formats never reach stderr by accident.
The redaction walks nested objects and arrays. Empty input returns an empty slice so verbose log lines render cleanly without extra branches at the call site.
Types ¶
type APIError ¶
type APIError struct {
StatusCode int
Code string `json:"code"`
Message string `json:"message"`
RawBody string
RetryAfter time.Duration
}
func ParseErrorResponse ¶
func (*APIError) FriendlyMessage ¶
type APIKeyInfo ¶
type CampaignContact ¶
type CampaignResponse ¶
type CampaignResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
TemplateName string `json:"templateName,omitempty"`
TemplateBody string `json:"templateBody,omitempty"`
TotalMessages int `json:"totalMessages"`
SentCount int `json:"sentCount,omitempty"`
DeliveredCount int `json:"deliveredCount,omitempty"`
ReadCount int `json:"readCount,omitempty"`
ClickedCount int `json:"clickedCount,omitempty"`
ConvertedCount int `json:"convertedCount,omitempty"`
ConvertedValue float64 `json:"convertedValue,omitempty"`
TotalCost float64 `json:"totalCost"`
ScheduledAt string `json:"scheduledAt,omitempty"`
StartedAt string `json:"startedAt,omitempty"`
FinishedAt string `json:"finishedAt,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
}
CampaignResponse is the unified shape used for create/list/get. Fields are a superset of what the three endpoints actually populate:
- POST /v1/campaigns returns the small CampaignResponse DTO (id, name, status, totalMessages, totalCost) — extra fields stay zero.
- GET /v1/campaigns returns the rich CampaignListItem (adds templateName, sentCount, createdAt) inside a Spring page wrapper — see ListCampaigns for the unwrap step.
- GET /v1/campaigns/{id} returns CampaignDetailResponse with the full funnel (delivered/read/clicked/converted) plus timestamps.
Keeping a single Go struct avoids forcing every caller to choose between three near-identical types. Callers should treat any field they don't expect for their endpoint as "zero, ignore".
func (*CampaignResponse) FailedCount ¶
func (campaign *CampaignResponse) FailedCount() int
FailedCount reports how many messages of the campaign never reached the "sent" state. The backend doesn't expose failedCount directly — it's inferred from totalMessages minus sentCount. Returns 0 if the CampaignResponse came from an endpoint that doesn't populate sentCount (e.g. POST /v1/campaigns), so display code should guard with a check like `if campaign.SentCount > 0 || campaign.TotalMessages == 0`.
type CaptureConflictError ¶
type CaptureConflictError struct {
ListenerID string
Owner string
StartedAt string
ExpiresAt string
Message string
}
CaptureConflictError is returned when another CLI session is already holding the org's capture slot. The CLI displays the conflict details (owner, expiresAt) so the user can decide whether to wait or kill the other session.
func (*CaptureConflictError) Error ¶
func (err *CaptureConflictError) Error() string
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
func NewClientFromConfig ¶
func (*Client) CreateAPIKey ¶
func (client *Client) CreateAPIKey(mode, name string) (*GeneratedAPIKey, error)
func (*Client) CreateCampaign ¶
func (client *Client) CreateCampaign(request CreateCampaignRequest, idempotencyKey string) (*CampaignResponse, error)
func (*Client) CreateWebhookListener ¶
func (client *Client) CreateWebhookListener(req CreateWebhookListenerRequest) (*CreateWebhookListenerResponse, error)
CreateWebhookListener registers an ephemeral capture session. Returns ErrCaptureNotSupported on 404 (graceful fallback) and CaptureConflictError on 409 (another session holds the slot).
func (*Client) DeleteTemplate ¶
func (*Client) DoWithHeaders ¶
func (*Client) EstimateCampaign ¶
func (client *Client) EstimateCampaign(templateName string, recipientCount int) (*CampaignEstimateResponse, error)
func (*Client) GetCampaign ¶
func (client *Client) GetCampaign(campaignID string) (*CampaignResponse, error)
func (*Client) GetContact ¶
func (client *Client) GetContact(phone string) (*ContactResponse, error)
func (*Client) GetMessageStatus ¶
func (client *Client) GetMessageStatus(messageID string) (*MessageResponse, error)
func (*Client) GetMessages ¶
func (*Client) GetTemplateStatus ¶
func (client *Client) GetTemplateStatus(templateID string) (*TemplateStatusResponse, error)
func (*Client) GetWalletBalance ¶
func (*Client) HeartbeatWebhookListener ¶
HeartbeatWebhookListener extends the listener's TTL. Returns nil on 204 success. On 410 (already expired), returns a sentinel so the CLI can surface "listener expired, re-run with --capture to grab a fresh slot".
func (*Client) ImportContacts ¶
func (client *Client) ImportContacts(contacts []ContactRequest) (string, error)
func (*Client) IsLiveMode ¶
func (*Client) ListAPIKeys ¶
func (client *Client) ListAPIKeys() ([]APIKeyInfo, error)
func (*Client) ListCampaigns ¶
func (client *Client) ListCampaigns() ([]CampaignResponse, error)
func (*Client) ListContacts ¶
func (*Client) ListNumbers ¶
func (client *Client) ListNumbers() ([]PhoneNumberResponse, error)
func (*Client) ListOrganizations ¶
func (client *Client) ListOrganizations() ([]Organization, error)
ListOrganizations fetches every organization accessible to the current authenticated user. Used by `arara org list` and `arara org use`.
func (*Client) ListTemplates ¶
func (*Client) PollDeviceToken ¶
func (client *Client) PollDeviceToken(options PollOptions) (*TokenResponse, error)
func (*Client) RefreshDeviceToken ¶
func (client *Client) RefreshDeviceToken(clientID, refreshToken string) (*TokenResponse, error)
func (*Client) ReleaseWebhookListener ¶
ReleaseWebhookListener tells the backend the CLI is done. Idempotent on the backend side. Best-effort from the CLI's perspective: failure here just means the listener will expire naturally on its TTL.
func (*Client) RequestDeviceCode ¶
func (client *Client) RequestDeviceCode(clientID, scope string) (*DeviceCodeResponse, error)
func (*Client) RevokeAPIKey ¶
func (*Client) SendMessage ¶
func (client *Client) SendMessage(request SendMessageRequest) (*MessageResponse, error)
func (*Client) SetVerbose ¶
func (*Client) StreamEvents ¶
StreamEvents connects to the SSE endpoint and returns a channel of events. The channel is closed when the context is cancelled or the connection is permanently lost. Temporary disconnections are retried with exponential backoff.
type ContactRequest ¶
type ContactResponse ¶
type ContactsListResponse ¶
type ContactsListResponse struct {
Contacts []ContactResponse `json:"contacts"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
TotalPages int `json:"totalPages"`
}
type CreateCampaignRequest ¶
type CreateCampaignRequest struct {
Name string `json:"name"`
TemplateName string `json:"templateName"`
Contacts []CampaignContact `json:"contacts"`
}
type CreateWebhookListenerRequest ¶
type CreateWebhookListenerRequest struct {
Owner string `json:"owner,omitempty"`
TTLSeconds int `json:"ttlSeconds,omitempty"`
}
CreateWebhookListenerRequest is the body of POST /v1/cli/webhook-listeners. Both fields are optional — the backend supplies sensible defaults when they're empty.
type CreateWebhookListenerResponse ¶
type CreateWebhookListenerResponse struct {
ID string `json:"id"`
Secret string `json:"secret"`
OrganizationID string `json:"organizationId"`
ExpiresAt string `json:"expiresAt"`
HeartbeatIntervalSeconds int `json:"heartbeatIntervalSeconds"`
}
CreateWebhookListenerResponse is the 201 body. Secret is shown ONCE and must not be persisted to disk by the CLI — it dies with the process.
type DeviceCodeRequest ¶
type DeviceCodeResponse ¶
type DeviceTokenRequest ¶
type DryRunPreview ¶
type DryRunPreview struct {
Method string `json:"method"`
Path string `json:"path"`
Payload SendMessageRequest `json:"payload"`
EstimatedCost *CampaignEstimateResponse `json:"estimatedCost,omitempty"`
EstimateError string `json:"estimateError,omitempty"`
}
type GeneratedAPIKey ¶
type GeneratedAPIKey struct {
PlainTextKey string `json:"plainTextKey"`
}
type MessageResponse ¶
type MessageResponse struct {
ID string `json:"id"`
Receiver string `json:"receiver"`
TemplateName string `json:"templateName,omitempty"`
Body string `json:"body,omitempty"`
Status string `json:"status"`
MessageType string `json:"messageType,omitempty"`
Mode string `json:"mode,omitempty"`
Sender string `json:"sender,omitempty"`
Cost float64 `json:"cost,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
}
func (*MessageResponse) UnmarshalJSON ¶
func (response *MessageResponse) UnmarshalJSON(data []byte) error
UnmarshalJSON tolerates `"id": null` from the backend. The Kotlin DTO declares `id: String?`, so although today every response has the id populated, the contract permits null. Without this custom unmarshaller, `json.Unmarshal` fails on null with "cannot unmarshal null into string" — we'd silently turn a successful send into an error. Mapping null → "" lets the CLI surface a "no id returned" message to the user instead.
type Organization ¶
type Organization struct {
ID string `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
Role string `json:"role"`
Mode string `json:"mode"`
}
Organization is a single org the authenticated user belongs to. Returned by GET /auth/me/organizations. Backend reads the user_organizations M2M table and unions in the user's primary org, so a single user can show up in N orgs with different roles per org. The CLI uses this to drive `arara org list` / `arara org use`.
type PhoneNumberResponse ¶
type PollOptions ¶
type RefreshTokenRequest ¶
type SSEEvent ¶
SSEEvent represents a single Server-Sent Event received from the stream.
When the backend wraps an event in an envelope (RFC 0003), Data carries only the inner payload JSON (the part the customer's webhook handler would normally receive) and Signature carries the pre-computed `sha256=<hex>` HMAC. Old-format events leave Signature empty and Data holds the raw `data:` line as-is.
type SendMessageRequest ¶
type Template ¶
type Template struct {
ID string `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
Language string `json:"language"`
Body string `json:"body"`
Header string `json:"header,omitempty"`
ProviderStatus string `json:"providerStatus,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
}