Documentation
¶
Overview ¶
Package carragent is a client for Carragent's public, read-only API: its AI agents for small businesses, and its plans and prices (https://carragent.com/developers).
The API needs no account and no key. This package reads it. It does not sign anyone up, start a trial, change anything or reach a business's data. It uses only the standard library.
The module path is carragent.com/go and the package is named carragent:
import carragent "carragent.com/go"
Reading ¶
Make a Client and call its methods. Every one takes a context first, and returns typed values:
client := carragent.NewClient()
results, err := client.Search(ctx, "late invoices", "")
if err != nil {
return err
}
for _, hit := range results.Data {
fmt.Println(hit.Kind, hit.ID, hit.Name)
}
agent, err := client.GetAgent(ctx, "collections", carragent.English)
// agent.Summary, agent.WaitsForOwner, agent.Never
plans, err := client.ListPlans(ctx, "")
// Prices are whole US cents, before taxes: plan.MonthlyCents, plan.YearlyCents.
Text comes in English, Spanish or French (Lang); the empty Lang is English. Client.IterAgents walks every agent, Client.Batch runs up to 20 reads in one request, and Client.CreateJob, Client.GetJob and Client.WaitJob are the REST pattern for slow operations, which these fast reads follow so that code written for one works unchanged.
Errors ¶
An error answer from the API is a *Problem (an RFC 9457 problem document). Branch on its Code, which is stable:
var problem *carragent.Problem
if errors.As(err, &problem) {
if problem.RateLimited() {
time.Sleep(problem.RetryAfter)
}
}
The client never retries by itself. A request that gets no answer returns the transport's error, which wraps the standard ones: errors.Is(err, context.DeadlineExceeded) is true when the context ran out.
Tokens ¶
A token is optional. It raises the rate limit from 60 requests a minute per IP address to 300 a minute per client, and reads the same public catalog. Client.UseAnonymousIdentity registers the agent anonymously and gets one; Client.UseClientCredentials does it for an OAuth client. Both set Client.Token. A token lasts an hour.
Concurrency ¶
A Client is safe for concurrent use as long as its fields are not changed meanwhile. UseAnonymousIdentity and UseClientCredentials change Token, so call them before sharing the client between goroutines.
Example ¶
package main
import (
"context"
"fmt"
"log"
carragent "carragent.com/go"
)
func main() {
ctx := context.Background()
client := carragent.NewClient()
// Which agent follows up late invoices?
results, err := client.Search(ctx, "late invoices", "")
if err != nil {
log.Fatal(err)
}
for _, hit := range results.Data {
fmt.Println(hit.Kind, hit.ID, hit.Name)
}
// What does it do, and what does it leave to the owner?
agent, err := client.GetAgent(ctx, "collections", carragent.English)
if err != nil {
log.Fatal(err)
}
fmt.Println(agent.Summary)
fmt.Println("Waits for the owner:", agent.WaitsForOwner)
fmt.Println("Never:", agent.Never)
// What does it cost? Prices are whole US cents, before taxes.
plans, err := client.ListPlans(ctx, "")
if err != nil {
log.Fatal(err)
}
for _, plan := range plans.Data {
if plan.SelfServe {
fmt.Printf("%s: $%.2f a month\n", plan.Name, float64(plan.MonthlyCents)/100)
}
}
fmt.Println("Free trial:", plans.Terms.TrialDays, "days")
}
Output:
Index ¶
- Constants
- type Agent
- type AgentIterator
- type AgentList
- type BatchItem
- type BatchResponse
- type BatchResult
- type Client
- func (c *Client) Batch(ctx context.Context, items []BatchItem) (*BatchResponse, error)
- func (c *Client) CreateJob(ctx context.Context, operation JobOperation, params map[string]any) (*JobAccepted, error)
- func (c *Client) GetAgent(ctx context.Context, id string, lang Lang) (*Agent, error)
- func (c *Client) GetJob(ctx context.Context, id string) (*Job, error)
- func (c *Client) GoString() string
- func (c *Client) IterAgents(params *ListAgentsParams) *AgentIterator
- func (c *Client) ListAgents(ctx context.Context, params *ListAgentsParams) (*AgentList, error)
- func (c *Client) ListPlans(ctx context.Context, lang Lang) (*PlanList, error)
- func (c *Client) RegisterAnonymous(ctx context.Context) (*Registration, error)
- func (c *Client) Search(ctx context.Context, q string, lang Lang) (*SearchResults, error)
- func (c *Client) String() string
- func (c *Client) UseAnonymousIdentity(ctx context.Context, registration *Registration) (*Token, error)
- func (c *Client) UseClientCredentials(ctx context.Context, clientID, secret string, options ...TokenOption) (*Token, error)
- func (c *Client) WaitJob(ctx context.Context, id string, interval time.Duration) (*Job, error)
- type Job
- type JobAccepted
- type JobLinks
- type JobOperation
- type JobResult
- type Lang
- type ListAgentsParams
- type Plan
- type PlanLimits
- type PlanList
- type PlanTerms
- type Problem
- type Registration
- type SearchHit
- type SearchResults
- type Token
- type TokenOption
Examples ¶
Constants ¶
const ( // DefaultBaseURL is where the API is. DefaultBaseURL = "https://carragent.com" // Version is this library's version. The client names it in its User-Agent header. Version = "1.0.0" )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Agent ¶
type Agent struct {
// ID is the agent's stable id, such as "collections".
ID string `json:"id"`
// Name is its display name.
Name string `json:"name"`
// Area is the business area: "money", "customers", "sales" or "operations".
Area string `json:"area"`
// Status is "live" when the agent works today and "soon" when it is being built.
Status string `json:"status"`
// Summary says what it does, in one line.
Summary string `json:"summary"`
// Runs says when it runs.
Runs string `json:"runs"`
// Steps is what it does, in order.
Steps []string `json:"steps"`
// WaitsForOwner is what waits for the owner's approval.
WaitsForOwner string `json:"waitsForOwner"`
// Never is what the agent never does.
Never string `json:"never"`
// Tools are the tools it connects to. It is nil when there is nothing to connect.
Tools []string `json:"tools"`
}
Agent is one AI agent.
type AgentIterator ¶
type AgentIterator struct {
// contains filtered or unexported fields
}
AgentIterator walks every agent, page by page. Make one with Client.IterAgents.
it := client.IterAgents(nil)
for it.Next(ctx) {
fmt.Println(it.Agent().Name)
}
if err := it.Err(); err != nil {
// ...
}
It is not safe for concurrent use.
func (*AgentIterator) Agent ¶
func (it *AgentIterator) Agent() Agent
Agent returns the agent Next moved to.
func (*AgentIterator) Err ¶
func (it *AgentIterator) Err() error
Err returns the error that stopped the iteration, or nil if it reached the last agent.
type AgentList ¶
type AgentList struct {
Object string `json:"object"`
Data []Agent `json:"data"`
// NextCursor goes in ListAgentsParams.Cursor to get the next page. It is empty on the last page.
NextCursor string `json:"nextCursor"`
HasMore bool `json:"hasMore"`
}
AgentList is a page of agents.
type BatchItem ¶
type BatchItem struct {
// ID names the item's answer: 1 to 64 of A-Za-z0-9_.:- and unique in the batch. The item's position when empty.
ID string `json:"id,omitempty"`
// Method is only "GET", which is also the default.
Method string `json:"method,omitempty"`
// Path is a read of the API relative to /api/v1: "/agents?limit=5", "/agents/collections", "/plans" or
// "/search?q=late%20invoices".
Path string `json:"path"`
}
BatchItem is one read in a batch.
type BatchResponse ¶
type BatchResponse struct {
Object string `json:"object"`
Responses []BatchResult `json:"responses"`
}
BatchResponse is the answers of a batch, in the order of the requests.
type BatchResult ¶
type BatchResult struct {
ID string `json:"id"`
// Status is the HTTP status the single endpoint would have answered.
Status int `json:"status"`
// Body is what the single endpoint would have returned: its JSON, or a problem when Status is 400 or more.
Body json.RawMessage `json:"body"`
}
BatchResult is the answer to one item of a batch.
func (BatchResult) Decode ¶
func (r BatchResult) Decode(v any) error
Decode reads the item's body into v, which is a pointer. It returns the item's problem instead when Status is 400 or more, so an item that failed is an error and one that worked is data.
type Client ¶
type Client struct {
// BaseURL is the API's address, without a path or with the prefix of a proxy. It must be https; http is
// accepted only for a server on this machine. Empty means DefaultBaseURL.
BaseURL string
// HTTPClient sends the requests. Nil means a client with a 10 second timeout that does not follow redirects:
// a redirect would send your token wherever it points, so it is returned as an error (code "unexpected_redirect").
// If you set your own, you choose its timeout and its redirect policy.
HTTPClient *http.Client
// Token is an access token to send as Authorization: Bearer. Empty sends none. UseAnonymousIdentity and
// UseClientCredentials get one and set it. A token lasts an hour; after that the API answers 401 invalid_token,
// even though it is public, until you get a new token or clear this one.
Token string
// UserAgent is the User-Agent header. Empty means "carragent-go/" and the Version. To name your application,
// start it with your own product token: "my-app/1.2 carragent-go/1.0.0".
UserAgent string
}
Client reads Carragent's public, read-only API: its AI agents for small businesses, and its plans and prices.
Everything it reads is public and needs no account or key. A token is optional: it only raises the rate limit (60 requests a minute per IP address without one, 300 a minute per client with one) and reads the same public catalog. Nothing here creates an account, starts a trial or touches a business's data.
The zero Client works, with the defaults NewClient sets. A Client keeps no connection of its own and never retries a request. It is safe for concurrent use as long as its fields are not changed meanwhile; UseAnonymousIdentity and UseClientCredentials change Token, so call them before sharing the client between goroutines.
func NewClient ¶
func NewClient() *Client
NewClient returns a Client with the defaults: DefaultBaseURL, a 10 second timeout and no token.
func (*Client) Batch ¶
Batch runs up to 20 reads in one request: POST /api/v1/batch.
The answers come back in order. An item that failed carries its problem in its Body and its own Status: Batch returns an error only when the whole request is refused. BatchResult.Decode reads an item's body, or returns its problem. Every item counts as one request against the rate limit.
Each item's Path is relative to /api/v1: "/agents", "/agents/collections", "/plans" or "/search?q=late%20invoices".
Example ¶
package main
import (
"context"
"fmt"
"log"
carragent "carragent.com/go"
)
func main() {
ctx := context.Background()
client := carragent.NewClient()
answers, err := client.Batch(ctx, []carragent.BatchItem{
{ID: "plans", Path: "/plans"},
{ID: "one", Path: "/agents/collections"},
{Path: "/search?q=appointment%20reminders"},
})
if err != nil {
log.Fatal(err) // the whole request was refused
}
for _, answer := range answers.Responses {
fmt.Println(answer.ID, answer.Status)
}
// An item that failed carries its problem; Decode returns it as an error.
var agent carragent.Agent
if err := answers.Responses[1].Decode(&agent); err != nil {
log.Fatal(err)
}
fmt.Println(agent.Name)
}
Output:
func (*Client) CreateJob ¶
func (c *Client) CreateJob(ctx context.Context, operation JobOperation, params map[string]any) (*JobAccepted, error)
CreateJob starts a job: POST /api/v1/jobs, which answers 202 Accepted.
This is the REST pattern for slow operations, so that code written for one works here unchanged. The operations are fast reads: nothing is queued, and a job's result is worked out when you poll it, from the catalog as it is then. Poll with GetJob or WaitJob.
params are the operation's query parameters as strings ("lang", "limit", "cursor", "id", "q"), or map[string]any{"requests": []BatchItem{...}} for OperationBatch. They may be nil.
Example ¶
package main
import (
"context"
"fmt"
"time"
carragent "carragent.com/go"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
client := carragent.NewClient()
accepted, err := client.CreateJob(ctx, carragent.OperationSearchCatalog, map[string]any{"q": "late invoices"})
if err != nil {
fmt.Println(err)
return
}
job, err := client.WaitJob(ctx, accepted.ID, time.Second)
if err != nil {
fmt.Println(err)
return
}
var results carragent.SearchResults
if err := job.Result.Decode(&results); err != nil {
fmt.Println(err)
return
}
fmt.Println(len(results.Data), "matches")
}
Output:
func (*Client) GetAgent ¶
GetAgent returns one agent by its id, such as "collections": GET /api/v1/agents/{id}. lang may be empty.
The error is a *Problem with the code "agent_not_found" (404) when there is no agent with that id.
func (*Client) GetJob ¶
GetJob returns a job, with its result once it has run: GET /api/v1/jobs/{id}.
The error is a *Problem with the code "job_not_found" (404) for an id the API does not recognise, and "job_expired" (410) once the job is older than 24 hours.
func (*Client) IterAgents ¶
func (c *Client) IterAgents(params *ListAgentsParams) *AgentIterator
IterAgents returns an iterator over every agent. It follows NextCursor from page to page and asks for a page only when Next needs it. params may be nil; its Cursor is where to start, and its Limit is the page size (50 by default, which is the largest).
Example ¶
package main
import (
"context"
"fmt"
"log"
carragent "carragent.com/go"
)
func main() {
ctx := context.Background()
client := carragent.NewClient()
it := client.IterAgents(&carragent.ListAgentsParams{Lang: carragent.Spanish})
for it.Next(ctx) {
fmt.Println(it.Agent().ID, "-", it.Agent().Summary)
}
if err := it.Err(); err != nil {
log.Fatal(err)
}
}
Output:
func (*Client) ListAgents ¶
ListAgents returns one page of the agents: GET /api/v1/agents. params may be nil.
func (*Client) ListPlans ¶
ListPlans returns the plans, with their prices in US cents (before taxes), their limits and the trial and refund terms: GET /api/v1/plans. There is one page: every plan. lang may be empty.
func (*Client) RegisterAnonymous ¶
func (c *Client) RegisterAnonymous(ctx context.Context) (*Registration, error)
RegisterAnonymous registers this agent anonymously: POST /agent/identity.
The answer holds an IdentityAssertion that is good for 24 hours. There is nothing to claim and no account behind it: it only gives your agent an identity of its own, so that its rate limit is its own. Keep the answer and pass it to UseAnonymousIdentity to get a token. Registrations are limited to 10 a minute per IP address.
Token is not sent to the registration.
func (*Client) Search ¶
Search is a keyword search over the agents and the plans, best match first: GET /api/v1/search.
It matches the words of q (those of three letters or more) against the text of the agents and plans in the language lang, ignoring case and accents. It is not a semantic search, so use the words the agents use, such as "late invoices" or "appointment reminders". It returns at most 10 matches. q is 1 to 200 characters; lang may be empty.
func (*Client) UseAnonymousIdentity ¶
func (c *Client) UseAnonymousIdentity(ctx context.Context, registration *Registration) (*Token, error)
UseAnonymousIdentity gets a token for an anonymous agent identity, and uses it from now on.
It registers first (RegisterAnonymous), unless you pass the registration you kept from an earlier call. It then exchanges the assertion for an access token at /api/oauth2/token (RFC 7523, the jwt-bearer grant) and sets Token. The token lasts an hour; call this again, with the same registration while it is valid, to get a new one. The assertion goes to the client's BaseURL, whatever address the registration names.
The error is a *Problem with the code "invalid_grant" (400) when the assertion is expired or not Carragent's. Token is left alone when it fails.
Example ¶
package main
import (
"context"
"log"
carragent "carragent.com/go"
)
func main() {
ctx := context.Background()
client := carragent.NewClient()
// Registers this agent anonymously, gets a token for it and uses it from now on. The token lasts an hour. Keep the
// registration (good for 24 hours) to get a new token without registering again.
registration, err := client.RegisterAnonymous(ctx)
if err != nil {
log.Fatal(err)
}
if _, err := client.UseAnonymousIdentity(ctx, registration); err != nil {
log.Fatal(err)
}
}
Output:
func (*Client) UseClientCredentials ¶
func (c *Client) UseClientCredentials(ctx context.Context, clientID, secret string, options ...TokenOption) (*Token, error)
UseClientCredentials gets a token with an OAuth client's id and secret, and uses it from now on (RFC 6749, client credentials).
The id and secret go to /api/oauth2/token as HTTP Basic credentials, and nowhere else. They are not kept. The token lasts an hour; call this again for a new one. Register a client at POST /api/oauth2/register (https://carragent.com/auth.md has the steps).
The error is a *Problem with the code "invalid_client" (401) for an unknown client or a wrong secret, and "invalid_scope" (400) for a scope beyond the client's. Token is left alone when it fails.
Example ¶
package main
import (
"context"
"log"
carragent "carragent.com/go"
)
func main() {
ctx := context.Background()
client := carragent.NewClient()
// An OAuth client from POST /api/oauth2/register (https://carragent.com/auth.md).
_, err := client.UseClientCredentials(ctx, "client id", "client secret", carragent.WithScope("agents:read plans:read"))
if err != nil {
log.Fatal(err)
}
}
Output:
func (*Client) WaitJob ¶
WaitJob polls a job until its status is no longer "pending", and returns it. It gives up with ctx's error when ctx is done, so give ctx a deadline.
Today a job has its result on the first poll, so this returns at once. Every poll runs the job again and costs what the job costs against the rate limit: one request, or one per item of a batch. interval is the time between polls; zero means one second.
type Job ¶
type Job struct {
Object string `json:"object"`
ID string `json:"id"`
// Status is "succeeded" once the job has run. Result says what the operation answered.
Status string `json:"status"`
Operation string `json:"operation"`
CreatedAt time.Time `json:"createdAt"`
CompletedAt time.Time `json:"completedAt"`
ExpiresAt time.Time `json:"expiresAt"`
Result JobResult `json:"result"`
}
Job is a job as polled: the answer to GetJob.
type JobAccepted ¶
type JobAccepted struct {
Object string `json:"object"`
ID string `json:"id"`
// Status is "pending".
Status string `json:"status"`
Operation string `json:"operation"`
CreatedAt time.Time `json:"createdAt"`
// ExpiresAt is when the job stops being available: 24 hours after it was created.
ExpiresAt time.Time `json:"expiresAt"`
Links JobLinks `json:"links"`
}
JobAccepted is a job that has just been created: the answer to CreateJob, a 202 Accepted.
type JobLinks ¶
type JobLinks struct {
// Self is the job's address, which GetJob polls.
Self string `json:"self"`
}
JobLinks says where a job is.
type JobOperation ¶
type JobOperation string
JobOperation is what a job runs.
const ( OperationListAgents JobOperation = "listAgents" OperationGetAgent JobOperation = "getAgent" OperationListPlans JobOperation = "listPlans" OperationSearchCatalog JobOperation = "searchCatalog" OperationBatch JobOperation = "batch" )
The operations a job can run.
type JobResult ¶
type JobResult struct {
// Status is the HTTP status the operation answered. It can be a 4xx: the job still succeeded.
Status int `json:"status"`
// Body is the operation's JSON, or a problem.
Body json.RawMessage `json:"body"`
}
JobResult is what a job's operation returned.
type Lang ¶
type Lang string
Lang is the language of the text the API returns. The empty Lang means English.
type ListAgentsParams ¶
type ListAgentsParams struct {
// Lang is the language of the text. Empty means English.
Lang Lang
// Limit is the page size, 1 to 50. Zero means the API's default, 20.
Limit int
// Cursor is the NextCursor of the previous page. Empty means the first page.
Cursor string
}
ListAgentsParams are the optional parameters of ListAgents. The zero value asks for the first page in English.
type Plan ¶
type Plan struct {
ID string `json:"id"`
Name string `json:"name"`
Summary string `json:"summary"`
ForWho string `json:"forWho"`
Recommended bool `json:"recommended"`
// Currency is always "USD".
Currency string `json:"currency"`
// MonthlyCents is the price a month in cents. For a plan that is quoted, it is the starting price.
MonthlyCents int `json:"monthlyCents"`
// YearlyCents is the price a year in cents. It is nil when the plan is quoted.
YearlyCents *int `json:"yearlyCents"`
// SelfServe is false when the plan is quoted by email rather than bought on the site.
SelfServe bool `json:"selfServe"`
Limits PlanLimits `json:"limits"`
}
Plan is one plan. Prices are whole US cents, before applicable taxes.
type PlanLimits ¶
type PlanLimits struct {
Agents *int `json:"agents"`
Connections *int `json:"connections"`
MessagesPerMonth *int `json:"messagesPerMonth"`
Seats *int `json:"seats"`
}
PlanLimits is what a plan includes. A nil limit means unlimited, or quoted.
type PlanList ¶
type PlanList struct {
Object string `json:"object"`
Data []Plan `json:"data"`
Terms PlanTerms `json:"terms"`
// NextCursor is always empty.
NextCursor string `json:"nextCursor"`
HasMore bool `json:"hasMore"`
}
PlanList is every plan and the terms. There is only one page.
type PlanTerms ¶
type PlanTerms struct {
TrialDays int `json:"trialDays"`
AnnualRefundDays int `json:"annualRefundDays"`
SetupFee int `json:"setupFee"`
Contract bool `json:"contract"`
Currency string `json:"currency"`
}
PlanTerms are the terms that apply to every plan.
type Problem ¶
type Problem struct {
// Type is the problem's type URI, "about:blank" for the API's own.
Type string `json:"type,omitempty"`
// Status is the HTTP status.
Status int `json:"status"`
// Code is the API's stable, machine-readable error code, such as "agent_not_found", "invalid_parameter" or
// "rate_limited"; for the OAuth endpoints, the RFC 6749 error ("invalid_client", "invalid_grant").
// The client adds four of its own: "http_error" (an error answer that is not a problem document, such as a
// gateway's HTML page), "invalid_response" (a success answer that is not the JSON it should be),
// "unexpected_redirect" and "response_too_large".
Code string `json:"code"`
// Title is a short summary of the problem.
Title string `json:"title"`
// Detail says what went wrong this time, when the API says.
Detail string `json:"detail,omitempty"`
// RetryAfter is how long to wait before trying again, from the Retry-After header of a 429 answer. It is zero
// when there was no such header, or when it said to retry at once.
RetryAfter time.Duration `json:"-"`
}
Problem is an error answer from the API: an RFC 9457 problem document (application/problem+json), or an OAuth error (RFC 6749) from the token endpoint. Every method of Client returns it as a *Problem, so use errors.As:
var problem *carragent.Problem
if errors.As(err, &problem) && problem.Code == "agent_not_found" {
// ...
}
Branch on Code, which is stable, rather than on Title or Detail, which are for people.
A request that gets no answer at all (no network, a refused connection, a timeout) is not a Problem: the method returns the transport's error, which wraps the standard ones, so errors.Is(err, context.DeadlineExceeded) works.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"time"
carragent "carragent.com/go"
)
func main() {
client := carragent.NewClient()
_, err := client.GetAgent(context.Background(), "no-such-agent", "")
var problem *carragent.Problem
if errors.As(err, &problem) {
switch {
case problem.RateLimited():
time.Sleep(problem.RetryAfter) // the client never retries by itself
case problem.Code == "agent_not_found":
fmt.Println("no such agent:", problem.Detail)
default:
fmt.Println(problem.Status, problem.Code, problem.Title)
}
}
}
Output:
func (*Problem) RateLimited ¶
RateLimited reports whether the API answered 429, too many requests. Wait RetryAfter and try again: the client never retries by itself.
type Registration ¶
type Registration struct {
RegistrationID string `json:"registration_id"`
RegistrationType string `json:"registration_type"`
// IdentityAssertion is a signed assertion, good for 24 hours, that UseAnonymousIdentity trades for a token.
IdentityAssertion string `json:"identity_assertion"`
AssertionExpires time.Time `json:"assertion_expires"`
PreClaimScopes []string `json:"pre_claim_scopes"`
TokenEndpoint string `json:"token_endpoint"`
GrantType string `json:"grant_type"`
}
Registration is an anonymous agent registration, the answer to RegisterAnonymous.
type SearchHit ¶
type SearchHit struct {
// Kind is "agent" or "plan".
Kind string `json:"kind"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
// Score is higher for a better match. Its scale is not part of the API.
Score float64 `json:"score"`
}
SearchHit is one match of a search: an agent or a plan.
type SearchResults ¶
type SearchResults struct {
Object string `json:"object"`
Query string `json:"query"`
Data []SearchHit `json:"data"`
// NextCursor is always empty.
NextCursor string `json:"nextCursor"`
HasMore bool `json:"hasMore"`
}
SearchResults are the matches of a search, best first. There is only one page.
type Token ¶
type Token struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
// ExpiresIn is the seconds until it expires: an hour.
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
Token is an access token for Authorization: Bearer.
type TokenOption ¶
type TokenOption func(*tokenRequest)
A TokenOption changes a token request.
func WithScope ¶
func WithScope(scope string) TokenOption
WithScope asks for these space-separated scopes ("agents:read plans:read search:read"), within the client's own. Without it, the token gets all of the client's scopes.