http_client
Skill3 starsChanged 3 months agoMITView on GitHub
github.com/jkaninda/okapi/client is a small fluent HTTP client with: Do() and Send() are aliases. For the common "do, decode, fail on non-2xx" path use Decode: Decode chooses JSON / XML / YAML based on the response Content-Type. Each verb returns a *RequestBuilder: Available verbs: Get, Post, Put, Patch, Delete, Head, Options. For anything else use c.Request(method, path). Two escape hatches on the client itself: Builders can override client defaults for a single call:
In this repository20 other files it gives its agents.
- authenticationSkill
- cliSkill
- contextSkill
- dynamic_routesSkill
- error_handlingSkill
- middlewareSkill
- openapiSkill
- overviewSkill
- request_bindingSkill
- responseSkill
- route_definitionSkill
- routingSkill
- sse_streamSkill
- std_compatSkill
- templatingSkill
- testingSkill
- tls_httpsSkill
- validationSkill
- websocketSkill
- web_spaSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 6
Default + per-request middleware chain
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Okapi HTTP Client (okapi/client package)
github.com/jkaninda/okapi/client is a small fluent HTTP client with:
- A request builder per verb
- Default + per-request middleware chain
- Built-in retry policy with exponential backoff
- Body encoders for JSON, XML, YAML, form, multipart, raw
- Response decoders driven by
Content-Type - Zero dependency on the Okapi server package — usable against any REST API
Quick Start
import "github.com/jkaninda/okapi/client"
c := client.New("https://api.example.com",
client.WithBearerToken(token),
client.WithUserAgent("my-app/1.0"),
client.WithTimeout(10*time.Second),
)
var user User
resp, err := c.Get("/users/42").
WithContext(ctx).
QueryParam("expand", "profile").
Do()
if err != nil { return err }
if err := resp.Error(); err != nil { return err } // *client.HTTPError on non-2xx
if err := resp.JSON(&user); err != nil { return err }Do() and Send() are aliases. For the common "do, decode, fail on non-2xx" path use Decode:
var user User
err := c.Get("/users/42").Decode(&user)Decode chooses JSON / XML / YAML based on the response Content-Type.
Client Options
| Option | Purpose |
WithHTTPClient(*http.Client) | Provide a pre-configured http.Client (TLS, transport) |
WithTimeout(d) | Default per-request timeout |
WithHeader(k, v) | Add one default header |
WithHeaders(map) | Merge multiple default headers |
WithBearerToken(token) | Sets Authorization: Bearer <token> |
WithBasicAuth(u, p) | Sets Authorization: Basic ... |
WithUserAgent(ua) | Sets the default User-Agent |
WithMiddleware(mw...) | Append middleware to the chain |
WithRetry(policy) | Default retry policy |
Request Builder
Each verb returns a *RequestBuilder:
resp, err := c.Post("/items").
WithContext(ctx).
Header("X-Trace-Id", traceID).
QueryParam("dry_run", "true").
JSONBody(Item{Title: "hello"}).
Timeout(5*time.Second).
Do()Available verbs: Get, Post, Put, Patch, Delete, Head, Options. For anything else use c.Request(method, path).
Two escape hatches on the client itself:
c.BaseURL() // the configured base URL
c.Do(ctx, req *http.Request) // dispatch a hand-built *http.Request through the middleware chainTerminal Methods
| Method | Behavior |
Do() | Issue the request, return (*Response, error) |
Send() | Alias for Do() |
Decode(target) | Do() + decode into target; returns *HTTPError on non-2xx |
Body Encoders
| Method | Content-Type |
JSONBody(v any) | application/json |
XMLBody(v any) | application/xml |
YAMLBody(v any) | application/yaml |
FormBody(map[string]string) | application/x-www-form-urlencoded |
Multipart(func(*multipart.Writer) error) | multipart/form-data; boundary=… |
RawBody([]byte) | unset (use Header to set) |
Body(io.Reader) | unset (use Header to set) |
Per-Request Auth Shortcuts
c.Get("/me").BearerToken(jwt).Send()
c.Get("/admin").BasicAuth("user", "pass").Send()Per-Request Overrides
Builders can override client defaults for a single call:
c.Get("/big").
Timeout(30 * time.Second).
Retry(client.RetryPolicy{MaxAttempts: 5, BaseDelay: 100 * time.Millisecond}).
Middleware(client.LoggingMiddleware(os.Stdout)).
Do()Response
resp.IsSuccess() // 2xx?
resp.Error() // *HTTPError on non-2xx, nil otherwise
resp.String() // body as string
resp.Body // []byte
resp.Decode(&target) // format chosen from Content-Type
resp.JSON(&target)
resp.XML(&target)
resp.YAML(&target)
resp.JSONPath("user.profile.name") // (any, bool) — dot-path lookup in a JSON object
resp.Cookie("sid") // *http.Cookie or nil
resp.Method / resp.URL // originating method and final URL
resp.Header // http.Header (from the embedded *http.Response)
resp.StatusCode // int (from the embedded *http.Response)Response embeds *http.Response and exposes the body as Body []byte — already read and closed when the response is returned, so decode from Body rather than reading a stream.
Middleware
type RoundTripFunc func(*http.Request) (*http.Response, error)
type Middleware func(next RoundTripFunc) RoundTripFuncOrder: client middlewares are outermost; per-request middlewares run next; the retry middleware sits innermost.
Built-in middlewares:
| Middleware | Behavior |
LoggingMiddleware(io.Writer) | One line per request (method, URL, status, duration) |
UserAgentMiddleware(ua) | Forces User-Agent on every request |
RequestIDMiddleware() | Sets X-Request-Id (random hex) if absent |
Custom middleware:
auth := func(next client.RoundTripFunc) client.RoundTripFunc {
return func(req *http.Request) (*http.Response, error) {
req.Header.Set("X-Service-Token", currentServiceToken())
return next(req)
}
}
c := client.New(baseURL, client.WithMiddleware(auth))Retry Policy
c := client.New(baseURL, client.WithRetry(client.RetryPolicy{
MaxAttempts: 4,
BaseDelay: 100 * time.Millisecond,
MaxDelay: 2 * time.Second,
}))Defaults:
MaxAttempts <= 1→ no retriesRetryOnStatusnil → retries on408,429,500,502,503,504MaxDelay == 0→ backoff doubles indefinitely- Transport errors (network failures) always retry while attempts remain
Custom retry predicate:
client.RetryPolicy{
MaxAttempts: 3,
BaseDelay: 50 * time.Millisecond,
ShouldRetry: func(resp *http.Response, err error) bool {
return err != nil || (resp != nil && resp.StatusCode == http.StatusBadGateway)
},
}Request bodies are buffered once and rewound between attempts, so retries work for POST/PUT/PATCH. Backoff is interrupted when the request context is cancelled.
Errors
resp, err := c.Get("/missing").Do()
if err != nil {
return err // transport / build error
}
if err := resp.Error(); err != nil {
var hErr *client.HTTPError
if errors.As(err, &hErr) {
fmt.Println(hErr.StatusCode, string(hErr.Body))
}
return err
}Do (and its alias Send) never returns HTTPError — a non-2xx response is a valid response. Opt in via resp.Error() or Decode, which calls Error() internally.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

