<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Igor</title>
    <description>The latest articles on DEV Community by Igor (@tigusigalpa).</description>
    <link>https://dev.to/tigusigalpa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1475742%2Fbaa73c6e-cca1-4aef-bf6a-e4c1f0ed9d5d.png</url>
      <title>DEV Community: Igor</title>
      <link>https://dev.to/tigusigalpa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tigusigalpa"/>
    <language>en</language>
    <item>
      <title>Stop Hand-Writing DefiLlama URLs: Meet defillama-go, a Go SDK for the Full API Surface</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Wed, 23 Sep 2026 10:35:37 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/stop-hand-writing-defillama-urls-meet-defillama-go-a-go-sdk-for-the-full-api-surface-gk5</link>
      <guid>https://dev.to/tigusigalpa/stop-hand-writing-defillama-urls-meet-defillama-go-a-go-sdk-for-the-full-api-surface-gk5</guid>
      <description>&lt;p&gt;If you are building a Go service around DeFi data, the first prototype is usually straightforward: make an HTTP request, decode JSON, and move on. The friction arrives later. One endpoint lives on a different host. A token identifier needs path escaping. A rate limit needs a sensible response. A Pro key must not leak into application logs. An upstream API adds a field that your struct did not expect.&lt;/p&gt;

&lt;p&gt;That is the gap &lt;a href="https://github.com/tigusigalpa/defillama-go" rel="noopener noreferrer"&gt;&lt;strong&gt;defillama-go&lt;/strong&gt;&lt;/a&gt; is designed to close. It is a small, idiomatic Go client for DefiLlama’s Free and Pro APIs that gives applications one context-aware interface for protocol TVL, token prices, stablecoins, yields, volumes, fees, bridges, real-world assets, equities, and more. Rather than presenting a thin collection of handwritten URLs, the library turns the API’s documented GET surface into discoverable Go services and methods. &lt;a href="https://github.com/tigusigalpa/defillama-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The headline is substantial but carefully scoped: the project maps &lt;strong&gt;all 132 GET operations in its pinned DefiLlama OpenAPI snapshot&lt;/strong&gt;—31 Free operations and 101 Pro operations—across 21 services. The route registry, generated service methods, and API index are kept in step by contract tests. That does not mean an SDK can eliminate the need to understand data semantics, but it does remove a surprising amount of transport plumbing from a production Go codebase. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/docs/api-index.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem is not simply “make a request”
&lt;/h2&gt;

&lt;p&gt;DefiLlama’s API is broad by design. Its documentation spans TVL, coins, stablecoins, yields, DEX and derivatives volumes, fees and revenue, bridges, protocol metrics, equities, pre-IPO data, and RWA endpoints. Some Free endpoints are served from &lt;code&gt;api.llama.fi&lt;/code&gt;; others use dedicated origins such as &lt;code&gt;coins.llama.fi&lt;/code&gt;, &lt;code&gt;stablecoins.llama.fi&lt;/code&gt;, or &lt;code&gt;yields.llama.fi&lt;/code&gt;. Pro routes use &lt;code&gt;pro-api.llama.fi&lt;/code&gt;. &lt;a href="https://api-docs.defillama.com/" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When that variety is handled ad hoc, every consumer makes its own decisions about URL construction, timeouts, parameter validation, error interpretation, retries, and authentication. Those choices are easy to get almost right and difficult to keep consistent. A client library earns its place when it centralizes those decisions without forcing a heavy framework on the rest of the application.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;defillama-go&lt;/code&gt; takes that approach. The package has no runtime dependencies outside Go’s standard library, uses the API’s per-operation origin rather than one assumed base URL, and lets callers inject their own &lt;code&gt;*http.Client&lt;/code&gt; when they need a proxy, tracing, custom TLS, or a shared timeout policy. The result is deliberately ordinary Go: construct a client, pass a &lt;code&gt;context.Context&lt;/code&gt;, call a service method, and handle a typed result or error. &lt;a href="https://github.com/tigusigalpa/defillama-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with useful Free data
&lt;/h2&gt;

&lt;p&gt;The shortest path into the SDK requires no API key. Install it into a Go 1.22+ module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/tigusigalpa/defillama-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a client and ask for protocol data and current prices. A request-scoped deadline is included because it is a good default for any network-facing service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="n"&gt;defillama&lt;/span&gt; &lt;span class="s"&gt;"github.com/tigusigalpa/defillama-go"&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TVL&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProtocols&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s TVL: $%.0f&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TVL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCurrentPrices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"coingecko:bitcoin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;btc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"coingecko:bitcoin"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BTC: $%.2f&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;btc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example illustrates the API shape that carries through the package. &lt;code&gt;Client&lt;/code&gt; exposes data domains such as &lt;code&gt;TVL()&lt;/code&gt;, &lt;code&gt;Prices()&lt;/code&gt;, &lt;code&gt;Stablecoins()&lt;/code&gt;, &lt;code&gt;Yields()&lt;/code&gt;, &lt;code&gt;Volumes()&lt;/code&gt;, and &lt;code&gt;Fees()&lt;/code&gt;. Methods accept &lt;code&gt;context.Context&lt;/code&gt; first. The client constructor does not make a network request, so invalid options fail early and constructing a shared client does not produce unexpected I/O. &lt;a href="https://github.com/tigusigalpa/defillama-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a Free endpoint, callers do not need to remember which DefiLlama host owns the operation. The route metadata does that work. This is a small convenience in a sample program and a meaningful reduction in copy-paste risk once a product needs price lookups, historical charts, pool data, and protocol summaries from the same service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Broad coverage without a flat, unwieldy API
&lt;/h2&gt;

&lt;p&gt;A full endpoint count is useful only if the interface remains navigable. &lt;code&gt;defillama-go&lt;/code&gt; groups its 132 mapped operations into 21 client-owned services. The following are representative starting points rather than a replacement for the full endpoint index. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/docs/api-index.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Service accessor&lt;/th&gt;
&lt;th&gt;Example method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Protocols, chains, TVL, and TVL charts&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TVL()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GetProtocols(ctx)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current and historical coin prices&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Prices()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GetCurrentPrices(ctx, coins)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supply and chart data for stablecoins&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Stablecoins()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GetStablecoins(ctx, opts)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free pools and Pro earn/borrow data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Yields()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GetPools(ctx)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DEX, options, and derivatives activity&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Volumes()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GetDEXOverview(ctx, opts)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fees and revenue summaries&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Fees()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GetOverview(ctx, opts)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bridges and transaction data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Bridges()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GetBridgeTransactions(ctx, id, opts)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ETFs, DAT, equities, pre-IPO, and RWA&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ETFs()&lt;/code&gt;, &lt;code&gt;DAT()&lt;/code&gt;, &lt;code&gt;Equities()&lt;/code&gt;, &lt;code&gt;PreIPO()&lt;/code&gt;, &lt;code&gt;RWA()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GetSnapshot(ctx)&lt;/code&gt;, &lt;code&gt;GetInstitutions(ctx)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The package documentation also covers treasury, oracle, fork, narrative, emissions, ecosystem, dimensions, financial-statement, and account-usage services. Each API operation has a corresponding entry in the project’s generated index, with the Go method and a link to the relevant official DefiLlama documentation. That index is especially helpful when moving from an API reference page to an implementation task. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/docs/api-index.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed models where the API is stable, raw payloads where it is not
&lt;/h2&gt;

&lt;p&gt;There is an important design choice behind a data client: pretending that every response is permanently stable can be as harmful as returning &lt;code&gt;map[string]any&lt;/code&gt; everywhere. &lt;code&gt;defillama-go&lt;/code&gt; uses typed models for response shapes that are stable enough to benefit from them, including &lt;code&gt;Protocol&lt;/code&gt;, &lt;code&gt;Chain&lt;/code&gt;, &lt;code&gt;CoinPrice&lt;/code&gt;, &lt;code&gt;Stablecoin&lt;/code&gt;, and &lt;code&gt;YieldPool&lt;/code&gt;. The package also preserves the complete raw payload on its stable typed models. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For endpoints whose responses are intentionally evolving, the library makes that flexibility explicit with clearly named &lt;code&gt;map[string]any&lt;/code&gt; or &lt;code&gt;[]any&lt;/code&gt; returns. For example, the Pro earn-pool query is exposed as a query-oriented result rather than a falsely rigid struct. That trade-off makes an upgrade less urgent when an upstream API adds a field, while still making the common, durable response shapes pleasant to use.&lt;/p&gt;

&lt;p&gt;The same restraint applies to numerical values. The client decodes API-supplied numbers as &lt;code&gt;float64&lt;/code&gt; transport values. That is appropriate for moving JSON across the boundary, but it is not a recommendation to use binary floating point for every monetary calculation. If an application needs exact rounding or auditable financial arithmetic, convert prices, TVL, and percentages to a decimal representation at the application boundary. DefiLlama’s data and metrics also follow DefiLlama’s own methodology; the SDK is a transport layer, not financial advice. &lt;a href="https://github.com/tigusigalpa/defillama-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Options make optional parameters visible
&lt;/h2&gt;

&lt;p&gt;Query-string work is where otherwise clean integrations often become opaque. This package uses option structs so that optional values are declared in Go rather than assembled as strings. Pointer fields are omitted when they are &lt;code&gt;nil&lt;/code&gt;, and the SDK validates declared enum values and required parameters before sending a request where applicable. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a concise example that asks for stablecoin pools on Ethereum with a minimum TVL threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Yields&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryEarnPools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EarnPoolsQuery&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Chain&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ethereum"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Stablecoin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;MinTVL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;_000_000&lt;/span&gt;&lt;span class="m"&gt;.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"received %d top-level fields&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not make every business rule automatic, nor should it. It does provide a reviewable declaration of what the request means. It also avoids serializing optional fields just because their Go zero value happens to be present.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production behavior is part of the API
&lt;/h2&gt;

&lt;p&gt;A library’s happy path is only half its value. In a service that calls remote data providers, cancellation, rate limits, transient failures, and actionable diagnostics should be designed rather than improvised.&lt;/p&gt;

&lt;p&gt;Every network method in &lt;code&gt;defillama-go&lt;/code&gt; accepts a context, and the constructed client is safe to share between goroutines. Retries are &lt;strong&gt;off by default&lt;/strong&gt;, which is an appropriately conservative choice for a library. When enabled with &lt;code&gt;WithRetryPolicy&lt;/code&gt;, retries are restricted to GET requests and retryable conditions: transient transport failures, HTTP 429 responses, and HTTP 5xx responses. The policy uses capped backoff, can add jitter, honors &lt;code&gt;Retry-After&lt;/code&gt; when it is longer, and stops when the context is cancelled or reaches its deadline. Ordinary 4xx responses and JSON decode errors are not retried. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The error model is similarly specific. Applications can use &lt;code&gt;errors.As&lt;/code&gt; to distinguish a missing resource, a rate limit, another API response, a transport failure, a malformed successful response, or an attempted Pro call without a key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TVL&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProtocol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"not-a-real-protocol"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;notFound&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotFoundError&lt;/span&gt;
    &lt;span class="n"&gt;rateLimit&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RateLimitError&lt;/span&gt;
    &lt;span class="n"&gt;apiErr&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIError&lt;/span&gt;
    &lt;span class="n"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransportError&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// HTTP 404: the requested protocol was not found.&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;rateLimit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// HTTP 429: rateLimit.RetryAfter may be non-zero.&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// Another non-2xx status with a redacted URL and response diagnostics.&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// DNS, TLS, timeout, or cancellation. errors.Is reaches the cause.&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error taxonomy matters in operations. A 404 may be a valid absence. A 429 may justify queueing or delaying work. A timeout might point to an overloaded dependency or an overly aggressive deadline. Treating all three as the same &lt;code&gt;error&lt;/code&gt; string makes monitoring and recovery much harder than they need to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pro API support that treats the key as sensitive
&lt;/h2&gt;

&lt;p&gt;DefiLlama Pro authenticates requests with the API key in the URL path. That detail deserves care because URLs often surface in logs, traces, proxy diagnostics, and wrapped network errors. The official documentation identifies &lt;code&gt;pro-api.llama.fi&lt;/code&gt; as the Pro request origin, and the SDK places the key in one escaped path segment as required by that convention. &lt;a href="https://github.com/tigusigalpa/defillama-go" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The library’s design goes beyond merely avoiding a custom header. Calling a Pro method without &lt;code&gt;WithAPIKey&lt;/code&gt; returns &lt;code&gt;*ProAPIKeyRequiredError&lt;/code&gt; before the SDK builds or sends a request. When a key is configured, the implementation redacts it from URLs, response diagnostics, headers, bodies, and nested transport errors. It also refuses to follow a redirect to a different origin for a Pro request, preventing the path-embedded credential from being forwarded elsewhere. Same-origin redirects can still work. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating a Pro client remains a normal Go configuration step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithAPIKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DEFILLAMA_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRetryPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defillama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetryPolicy&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;MaxAttempts&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;BaseDelay&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;250&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxDelay&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Jitter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TVL&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProtocolTVLChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"aave"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also an explicit &lt;code&gt;WithPreferProForFree(true)&lt;/code&gt; option. If a Pro key is available, it routes the 31 Free operations that have an official Pro mapping through their Pro equivalents. The important word is &lt;strong&gt;explicit&lt;/strong&gt;: configuring a key alone does not silently change the default routing behavior. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What to expect from a young library
&lt;/h2&gt;

&lt;p&gt;The project’s changelog identifies &lt;code&gt;v0.1.0&lt;/code&gt; as the initial release, so the right way to evaluate it is not as a decades-old dependency with an enormous ecosystem. Instead, look at whether its scope is honest and whether its maintenance mechanics are visible. The repository includes an MIT license, contribution guidance, a security policy, GitHub Actions for CI, tests, CodeQL, coverage, and releases. Its CI matrix runs formatting checks, &lt;code&gt;go vet&lt;/code&gt;, race-enabled tests, coverage, and builds against Go 1.22 and the stable Go release. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;1&lt;/a&gt; &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/.github/workflows/ci.yml" rel="noopener noreferrer"&gt;5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The endpoint coverage is tied to a pinned OpenAPI snapshot. That is good for repeatability, but it also means newly added upstream operations require the SDK to update its snapshot and regenerate the affected route and documentation artifacts. The package currently targets GET operations, so it should not be mistaken for a generalized client for hypothetical future write endpoints. Finally, data fields returned as floats deserve deliberate conversion in code paths where precision matters. These are useful boundaries to understand before adopting any financial-data transport library. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical starting point
&lt;/h2&gt;

&lt;p&gt;For a dashboard, analytics service, alerting job, portfolio research tool, or backend that needs DefiLlama data in Go, the next step is intentionally small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/tigusigalpa/defillama-go.git
&lt;span class="nb"&gt;cd &lt;/span&gt;defillama-go
go run ./examples/basic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository’s basic example calls Free endpoints; the separate Pro example demonstrates environment-based key loading, retry configuration, rate-limit handling, usage retrieval, and a Pro TVL chart request. From there, browse the &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/docs/api-index.md" rel="noopener noreferrer"&gt;endpoint index&lt;/a&gt;, choose the service that matches the data product you are building, and keep application-specific calculations and validation on your side of the boundary. &lt;a href="https://github.com/tigusigalpa/defillama-go/blob/main/docs/api-index.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;defillama-go&lt;/code&gt; is appealing not because it hides DefiLlama behind magic, but because it makes the integration boring in the best sense of the word. It centralizes API routing, context propagation, parameter encoding, error handling, retries, and key redaction while leaving Go applications in control of their HTTP client, timeouts, precision policy, and domain decisions. For teams that would rather spend time using DeFi data than maintaining endpoint glue, that is a solid foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>go</category>
      <category>defillama</category>
      <category>cryptocurrency</category>
      <category>bitcoin</category>
    </item>
    <item>
      <title>bitquery-go: A Production-Minded Go SDK for Bitquery GraphQL</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Tue, 22 Sep 2026 07:23:28 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/bitquery-go-a-production-minded-go-sdk-for-bitquery-graphql-5bdo</link>
      <guid>https://dev.to/tigusigalpa/bitquery-go-a-production-minded-go-sdk-for-bitquery-graphql-5bdo</guid>
      <description>&lt;p&gt;Blockchain data services rarely fail because sending a GraphQL request is difficult. They fail at the seams: a token appears in a log, an overloaded endpoint receives an avoidable retry storm, a large on-chain amount is decoded as an imprecise floating-point number, or a reconnecting stream is treated as exactly-once delivery. These are not glamorous problems, but they are the problems that determine whether an integration survives contact with production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tigusigalpa/bitquery-go" rel="noopener noreferrer"&gt;&lt;strong&gt;bitquery-go&lt;/strong&gt;&lt;/a&gt; is an MIT-licensed Go SDK for Bitquery GraphQL that takes those seams seriously. Rather than wrapping every possible field in a sprawling generated surface, it combines a small set of practical helpers with a direct, inspectable GraphQL operation API. More importantly, it makes the meaningful boundaries visible: Bitquery V1 and V2 are separate clients, HTTP queries and WebSocket subscriptions are separate workflows, and GraphQL-level errors are not confused with transport failures. &lt;a href="https://github.com/tigusigalpa/bitquery-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a library for Go teams building indexers, analytics backends, monitoring services, trading-data pipelines, or internal blockchain-data tools. It does not promise to abstract away the Bitquery schema. It gives developers a clear way to work with that schema while adding the safeguards that should surround an API client in a real service.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Core design choice:&lt;/strong&gt; V1 and V2 remain distinct contracts. The SDK does not rewrite a GraphQL document, switch endpoints, or silently fall back from one version to the other. &lt;a href="https://github.com/tigusigalpa/bitquery-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Start with the correct API contract
&lt;/h2&gt;

&lt;p&gt;The first useful feature of &lt;code&gt;bitquery-go&lt;/code&gt; is also the least flashy: it refuses to pretend that the two Bitquery APIs are interchangeable. The package exposes a &lt;code&gt;v1.Client&lt;/code&gt; for the historical HTTPS GraphQL API and a &lt;code&gt;v2.Client&lt;/code&gt; for the streaming GraphQL API over HTTPS. Live V2 updates use a separate &lt;code&gt;subscription.Client&lt;/code&gt; over WebSocket. That separation matters because the schemas, endpoint families, coverage, and migration expectations differ. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/version-coverage.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a new integration, V2 is the natural place to begin when the required EVM or Solana data is available. A legacy V1 document remains a valid reason to use V1, but migration should be checked query by query rather than assumed. The SDK helps rather than obstructs this decision: it can surface typed deprecation notices for V1 calls on documented deprecated networks, without blocking a deliberate legacy request. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/version-coverage.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result is a more honest architecture. A codebase can say exactly which API contract it depends on, instead of hiding an important data decision behind a generic “client.” That makes reviews, upgrades, and incident response easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install once, then keep GraphQL visible
&lt;/h2&gt;

&lt;p&gt;The package requires Go 1.21 or newer and installs in the usual way. Client creation is local; no network request occurs until an operation is executed. &lt;a href="https://github.com/tigusigalpa/bitquery-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/tigusigalpa/bitquery-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A V2 query can remain idiomatic Go without turning GraphQL into string concatenation. The &lt;code&gt;Operation&lt;/code&gt; keeps the operation name, query, and variables in separate fields. Values belong in &lt;code&gt;Variables&lt;/code&gt;, where they can be encoded safely and inspected independently from the document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="n"&gt;bitquery&lt;/span&gt; &lt;span class="s"&gt;"github.com/tigusigalpa/bitquery-go"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/tigusigalpa/bitquery-go/v2"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;latestBlocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewStaticTokenProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BITQUERY_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRegion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegionUS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;OperationName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"LatestBlocks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`query LatestBlocks($network: evm_network!) {
            EVM(network: $network) {
                Blocks(limit: {count: 3}) { Block { Number Time } }
            }
        }`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"network"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"eth"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DecodeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is a strong fit for an evolving GraphQL API. The SDK offers thin helpers for common operations, including selected EVM DEX trades, transfers, and transactions, plus selected Solana operations. However, raw &lt;code&gt;Execute&lt;/code&gt; remains the primary compatibility escape hatch when a team needs a newer cube or field. Instead of waiting for an SDK release to expose every schema addition, a developer can use the current Bitquery schema and keep the operation under version control. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/version-coverage.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat authentication and endpoints as configuration, not application logic
&lt;/h2&gt;

&lt;p&gt;The library supports two safe token paths: a pre-minted access token via &lt;code&gt;NewStaticTokenProvider&lt;/code&gt;, or an OAuth client-credentials provider. The latter caches the token and coalesces concurrent refresh work, which is the behavior a multi-goroutine service needs when a token is nearing expiry. &lt;a href="https://github.com/tigusigalpa/bitquery-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Endpoint selection is similarly explicit. Europe is the default region, while Asia and US can be chosen through options. A service can override the HTTP base URL or WebSocket URL for a private proxy or test server, with validation for absolute HTTP(S) and WS(S) URLs. The client will not accept a credential-bearing custom endpoint. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/version-coverage.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That final detail is worth noticing. For WebSocket authentication, Bitquery requires the OAuth token in the URL query string. &lt;code&gt;bitquery-go&lt;/code&gt; adds that query parameter internally, redacts it from errors and logger output, and tells callers not to build it into a custom endpoint themselves. This is the type of defensive default that prevents a momentary debugging shortcut from becoming a credential leak. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/error-handling.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Make errors useful without throwing away GraphQL data
&lt;/h2&gt;

&lt;p&gt;GraphQL changes the usual “non-200 equals failure” mental model. An HTTP 200 response can contain &lt;code&gt;errors[]&lt;/code&gt;, and it can still contain usable partial data. &lt;code&gt;bitquery-go&lt;/code&gt; preserves that distinction.&lt;/p&gt;

&lt;p&gt;By default, &lt;code&gt;Execute&lt;/code&gt; returns the response even when GraphQL errors are present, allowing application code to inspect &lt;code&gt;response.Errors&lt;/code&gt; and decide whether partial data is acceptable. A strict mode is available when the application wants any GraphQL error returned as a typed &lt;code&gt;KindGraphQL&lt;/code&gt; error. Crucially, strict-mode errors retain the full response, so partial data is still available for an explicit business decision. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/error-handling.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Transport and API failures also arrive through &lt;code&gt;*bitquery.Error&lt;/code&gt;, enabling code to distinguish authentication, authorization, plan-entitlement, rate-limit, server, GraphQL, subscription, and configuration failures. That is much more actionable than branching on error-message text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KindRateLimited&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Respect apiErr.RetryAfter and reduce pressure if needed.&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KindPlanEntitlement&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// The request is outside the plan; retrying will not help.&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KindAuthentication&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Refresh or replace the credentials used by the provider.&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KindServer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Temporary failure: apply service-level observability and policy.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default retry policy is deliberately conservative: up to four attempts with jittered exponential backoff, capped at 60 seconds, with &lt;code&gt;Retry-After&lt;/code&gt; taking precedence when the service sends it. It retries transient transport conditions, selected 5xx responses, rate limits, and documented shared-compute blocks. It does &lt;strong&gt;not&lt;/strong&gt; automatically replay mutations or HTTP subscriptions, because repeating those operations may be unsafe for the calling application. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/error-handling.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This policy is a sensible baseline rather than a hidden “reliability” switch. A workload can disable retries, add a rate limiter, set a timeout, and still use its own worker limits. In particular, the SDK does not fan out expensive queries or invent concurrency on the caller’s behalf. That makes capacity ownership clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve numeric precision where blockchain data needs it
&lt;/h2&gt;

&lt;p&gt;On-chain values are a poor match for casual &lt;code&gt;float64&lt;/code&gt; handling. Token amounts, decimals, identifiers, and block heights may exceed the range in which a floating-point representation is exact. &lt;code&gt;bitquery-go&lt;/code&gt; deliberately leaves response data as &lt;code&gt;json.RawMessage&lt;/code&gt; and uses &lt;code&gt;json.Number&lt;/code&gt; when decoding into interface values, so the application can decide whether a field belongs in &lt;code&gt;math/big&lt;/code&gt;, a decimal package, or a domain-specific type. &lt;a href="https://github.com/tigusigalpa/bitquery-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is an understated but important design decision. It avoids a library-level conversion that looks convenient in a demo and quietly corrupts large values in a financial or reconciliation workflow. The caller has a small additional responsibility, but it is the correct responsibility: choose the numeric representation that matches the business meaning of the field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build subscriptions as durable workers, not request handlers
&lt;/h2&gt;

&lt;p&gt;The subscription package is equally pragmatic. Live V2 GraphQL subscriptions are exposed by &lt;code&gt;subscription.Client&lt;/code&gt;, not bolted onto the ordinary HTTP client. That design communicates the operational reality: a subscription is a long-lived worker with cancellation, reconnection, queueing, and downstream idempotency concerns. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/websocket-lifecycle.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A subscription client supports both &lt;code&gt;graphql-transport-ws&lt;/code&gt; and the legacy &lt;code&gt;graphql-ws&lt;/code&gt; protocol. It handles the protocol lifecycle, keepalives, bounded reconnects, and clean shutdown. Its event queue is bounded so a slow consumer cannot grow memory without limit. Applications can choose &lt;code&gt;drop_oldest&lt;/code&gt;, which keeps recent events and exposes a dropped-event count, or &lt;code&gt;fail&lt;/code&gt;, which stops the stream with a typed error rather than losing an event silently. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/websocket-lifecycle.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;workerCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotifyContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interrupt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithSubProtocol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubProtocolGraphQLTransportWS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithSubscriptionQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OverflowDropOldest&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workerCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bitquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`subscription {
        EVM(network: eth) { Blocks { Block { Number Time } } }
    }`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Persist a stable event key before applying the payload.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final comment is not ceremonial. Realtime messages should be treated as at-least-once, and portions may be unordered. A production consumer should persist a stable, domain-appropriate deduplication key and backfill a missed interval with an HTTP query after a longer outage. The SDK provides the lifecycle machinery, but it does not make an impossible exactly-once guarantee on behalf of a downstream database or business process. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/websocket-lifecycle.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where bitquery-go fits best
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bitquery-go&lt;/code&gt; is compelling when a Go team wants a dependable client boundary without surrendering control of its GraphQL documents. It suits services that need a straightforward HTTP query path today and may later add live updates. It also suits teams maintaining a V1 integration that need to recognize its legacy status without breaking it through an automatic migration.&lt;/p&gt;

&lt;p&gt;It is intentionally not a generated, exhaustive Go model of every Bitquery cube and field. That is a feature for projects where the live schema evolves faster than a typed SDK can be regenerated and reviewed. Use the Bitquery IDE and current schema as the authority for data availability; keep important operations in the application; use this package for authentication, endpoint management, execution, error handling, rate limits, cancellation, and subscriptions. &lt;a href="https://github.com/tigusigalpa/bitquery-go/blob/main/docs/version-coverage.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository also includes runnable examples for HTTP, OAuth client credentials, subscriptions, V1 historical queries, V2 Solana transfers, and custom endpoints. Examples compile without credentials and only make a live request when the required environment variables are supplied. That makes them practical starting points for an internal spike or a production integration review. &lt;a href="https://github.com/tigusigalpa/bitquery-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A small SDK with the right boundaries
&lt;/h2&gt;

&lt;p&gt;The most valuable API libraries are not those that hide every underlying detail. They are the ones that hide repetitive plumbing while making consequential behavior explicit. &lt;code&gt;bitquery-go&lt;/code&gt; does that well: it keeps V1 and V2 honest, keeps GraphQL operations inspectable, protects sensitive token material in diagnostics, treats partial responses correctly, preserves numeric precision, and gives streaming workloads the controls they actually need.&lt;/p&gt;

&lt;p&gt;If you are building Bitquery-backed software in Go, start with the README, run the example closest to your workload, and make the version and delivery semantics part of your design from day one. You will spend less time untangling invisible client behavior later—and more time building the blockchain-data product your users actually need. &lt;a href="https://github.com/tigusigalpa/bitquery-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>go</category>
      <category>cryptocurrency</category>
      <category>bitcoin</category>
      <category>bitquery</category>
    </item>
    <item>
      <title>Goldsky from Go, Without the Glue Code: Introducing goldsky-go</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Wed, 09 Sep 2026 06:00:37 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/goldsky-from-go-without-the-glue-code-introducing-goldsky-go-4b1k</link>
      <guid>https://dev.to/tigusigalpa/goldsky-from-go-without-the-glue-code-introducing-goldsky-go-4b1k</guid>
      <description>&lt;p&gt;If your Go service talks to blockchain data through Goldsky, the hard part should be the product logic—not repeatedly rebuilding HTTP requests, deciding which failures are safe to retry, or chasing pagination tokens across several APIs. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;&lt;strong&gt;goldsky-go&lt;/strong&gt;&lt;/a&gt; is a small, community-maintained Go SDK that packages those integration details into an idiomatic client while deliberately keeping application-defined data flexible.&lt;/p&gt;

&lt;p&gt;The library targets the Goldsky REST API v1.2.0 and maps all 40 documented REST operations. It also provides first-class access to Subgraph GraphQL endpoints and Goldsky Edge HTTPS JSON-RPC. That means one Go package can cover the control-plane work of managing pipelines, subgraphs, webhooks, and Edge endpoints, as well as the data-plane work of querying indexed data and calling an RPC endpoint. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;goldsky-go&lt;/code&gt; is a community SDK, not an official Goldsky package. Its purpose is to make the supported APIs pleasant and predictable for Go applications while staying explicit about the boundaries of the underlying platform. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why a Go SDK is useful here
&lt;/h2&gt;

&lt;p&gt;Direct HTTP is always an option. It is also where subtle operational concerns begin to accumulate: authorization headers, escaped path parameters, bounded response bodies, request deadlines, error formats, pagination state, JSON-RPC envelope validation, multipart upload behavior, and retry policy. A thin SDK earns its place when it removes this repeated plumbing &lt;strong&gt;without hiding the decisions that matter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is the design direction of &lt;code&gt;goldsky-go&lt;/code&gt;. Its public methods accept &lt;code&gt;context.Context&lt;/code&gt;, its stable API shapes use typed request and response models, and its GraphQL data remains raw JSON because a subgraph schema belongs to the consuming application. The result is Go code that can be concise without pretending that all Web3 data has one static schema. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project also has no third-party runtime dependencies. For services where dependency surface area and deployment simplicity matter, that is a practical advantage: the package is built around the Go standard library rather than a large HTTP abstraction stack. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a reusable client
&lt;/h2&gt;

&lt;p&gt;The minimum installation requirement is Go 1.22 or later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/tigusigalpa/goldsky-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the client once, configure an application-appropriate timeout, and reuse it. A project API token authenticates REST calls and private GraphQL queries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="n"&gt;goldsky&lt;/span&gt; &lt;span class="s"&gt;"github.com/tigusigalpa/goldsky-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;goldsky&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GOLDSKY_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;goldsky&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pipelines&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;goldsky&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListPipelinesOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PageSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%-30s %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This small example illustrates two conventions that scale well in production. The client carries reusable configuration, while each operation receives its own deadline through &lt;code&gt;context.Context&lt;/code&gt;. The former sets a safety net; the latter lets a caller budget time according to the job at hand. The constructor validates options but does not make a network call, which also makes application startup and tests more predictable. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One package across the Goldsky surface area
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;goldsky-go&lt;/code&gt; organizes its functionality around services rather than requiring callers to assemble endpoint URLs manually. The following table is a useful high-level map.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Typical work&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Pipelines&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Validate, create, inspect, pause, resume, restart, delete, and observe Turbo Pipelines.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Subgraphs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deploy subgraph bundles, manage versions and tags, and read indexing logs or webhook entities.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Webhooks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create, list, and delete entity webhooks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Edge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manage Edge endpoints, keys, lifecycle actions, and metrics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Catalogs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Discover available subgraph chains, Edge networks, and Edge Data sources.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GraphQL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Send public or private Subgraph GraphQL queries.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RPC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Make individual or batch JSON-RPC 2.0 calls through Goldsky Edge.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full method-to-operation mapping is maintained in the repository’s API coverage document. &lt;a href="https://github.com/tigusigalpa/goldsky-go/blob/main/docs/api-coverage.md" rel="noopener noreferrer"&gt;2&lt;/a&gt; The broader point is convenience with a clear boundary: REST management, GraphQL querying, and HTTPS RPC have different protocols and failure modes, but a Go service can work with each through one familiar client model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat pagination as a protocol, not as a slice
&lt;/h2&gt;

&lt;p&gt;A common integration bug is assuming that a page with fewer records than the requested size must be the last page. Goldsky pagination instead uses a continuation token. &lt;code&gt;goldsky-go&lt;/code&gt; exposes pagers for pipelines, subgraphs, and Edge endpoints, and finishes only when there is no next-page token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;pager&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subgraphs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSubgraphPager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;goldsky&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListSubgraphsOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PageSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NextPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subgraph&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subgraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subgraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subgraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Health&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasMore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps continuation-token management out of the application loop. The pager also rejects invalid sizes locally, and after it reaches the final page, subsequent calls return an empty page instead of causing needless HTTP requests. Pagers themselves are not intended for concurrent use; the reusable client and its services are safe to share. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy subgraphs without first loading the whole bundle
&lt;/h2&gt;

&lt;p&gt;Deployment is one place where the mechanics of an HTTP client matter. &lt;code&gt;Subgraphs.Deploy&lt;/code&gt; streams a zip bundle as &lt;code&gt;multipart/form-data&lt;/code&gt;, rather than buffering the entire archive in application memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"build.zip"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;subgraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subgraphs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"my-subgraph"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;goldsky&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeploySubgraphOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Bundle&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;BundleFilename&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"build.zip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"deployed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subgraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subgraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That streaming behavior is paired with an important safety choice: deployments are not automatically retried. An arbitrary &lt;code&gt;io.Reader&lt;/code&gt; cannot necessarily be replayed, and an ambiguous timeout on a mutation can otherwise create duplicate or confusing outcomes. The practical pattern is to reopen the file and retry only after checking whether the first request took effect. This is a good example of the library choosing an explicit operational rule over a superficially convenient one. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Query GraphQL, then decode the schema you own
&lt;/h2&gt;

&lt;p&gt;Goldsky’s Subgraph GraphQL endpoints are useful precisely because different subgraphs can expose different entities and fields. Instead of generating brittle universal types, &lt;code&gt;goldsky-go&lt;/code&gt; returns GraphQL &lt;code&gt;data&lt;/code&gt; as &lt;code&gt;json.RawMessage&lt;/code&gt;, alongside errors, headers, and HTTP status. Your application then decodes the query result into the structure it expects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GraphQL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryPrivate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;projectID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"my-subgraph"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;goldsky&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GraphQLRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"{ _meta { block { number } } }"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasErrors&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;graphQLError&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errors&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graphQLError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Meta&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Block&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Number&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="s"&gt;`json:"number"`&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s"&gt;`json:"block"`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="s"&gt;`json:"_meta"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"indexed through block"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two query paths: &lt;code&gt;QueryPrivate&lt;/code&gt; uses the project token, while &lt;code&gt;QueryPublic&lt;/code&gt; can be used with a tokenless data client. A tokenless client will reject REST and private GraphQL access locally before any outbound request. This separation makes it easier to issue services the least privilege they need: a public data consumer does not need control-plane credentials. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Call Edge JSON-RPC without putting a secret in the URL
&lt;/h2&gt;

&lt;p&gt;For Goldsky Edge, the SDK supports HTTPS JSON-RPC 2.0, including batch calls. The Edge key is sent through &lt;code&gt;X-ERPC-Secret-Token&lt;/code&gt;, so it is not placed in a query string or endpoint URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;rpcClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;goldsky&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDataClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;goldsky&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithEdgeAPIKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GOLDSKY_EDGE_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;blockHex&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rpcClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RPC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"eth_blockNumber"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;blockHex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"latest block:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blockHex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Batch responses deserve special attention because JSON-RPC permits responses to arrive in any order. &lt;code&gt;RPC.Batch&lt;/code&gt; associates responses back to the input calls by request ID and validates malformed envelopes, duplicate IDs, unknown IDs, and invalid result/error combinations. Still, a transport-level success does not guarantee that every batch item succeeded, so applications should inspect each response’s &lt;code&gt;Error&lt;/code&gt; field. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The current Edge scope is intentionally HTTPS JSON-RPC only; WebSockets and subscriptions are outside the library’s supported surface. Being clear about this limitation helps teams choose the SDK for the right workloads rather than discovering it after an architecture is committed. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Errors and retries designed for production decisions
&lt;/h2&gt;

&lt;p&gt;A client library should make failures easier to handle, not just easier to print. REST errors are represented as RFC 9457 problem details, which allows code to branch on a stable problem type or status helper instead of parsing a human-readable error string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;goldsky&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsProblem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNotFound&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"resource does not exist"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsRateLimited&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetryAfter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"retry after"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"seconds"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The retry defaults are conservative. Safe read methods (&lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;, and &lt;code&gt;OPTIONS&lt;/code&gt;) may be retried for transport errors and selected transient HTTP statuses, with capped exponential backoff, jitter, and &lt;code&gt;Retry-After&lt;/code&gt; support. Mutating requests are not retried unless the application explicitly opts in with &lt;code&gt;WithRetryMutations()&lt;/code&gt;. Since Goldsky does not document idempotency keys for those mutations, that default protects callers from accidentally duplicating creates or updates after uncertain network failures. Streaming deployments remain single-attempt even when mutation retries are enabled. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Responses are also bounded in memory by default. REST, GraphQL, and RPC bodies are limited to 16 MiB unless the application deliberately chooses a larger maximum. This kind of default is not glamorous, but it is useful protection when a service is exposed to unexpected upstream behavior. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks: verify before you process
&lt;/h2&gt;

&lt;p&gt;Goldsky webhook deliveries include a shared secret in the literal &lt;code&gt;goldsky-webhook-secret&lt;/code&gt; header. The package provides constant-time comparison through &lt;code&gt;VerifyWebhookRequest&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handleWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;goldsky&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VerifyWebhookRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storedSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid webhook secret"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Cap and read the body, enqueue idempotent work, then acknowledge.&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNoContent&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The documented header is a shared secret, not an HMAC signature. Therefore, authenticate the request before reading or processing its body, keep the secret protected like any credential, and make downstream handling idempotent because deliveries may be retried. The SDK provides the comparison primitive; reliable event processing remains an application responsibility. &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical fit for Go teams building on Goldsky
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;goldsky-go&lt;/code&gt; will be most useful to Go teams that want a typed, testable client for Goldsky’s supported APIs but do not want a large framework or a generated client dictating every application data shape. It is especially compelling when one service needs to manage infrastructure through REST, deploy or observe subgraphs, query GraphQL, and call Edge RPC from the same codebase.&lt;/p&gt;

&lt;p&gt;Its value is not only the number of endpoints covered. The library codifies the unglamorous details that shape reliable integrations: context propagation, escaping, pagination termination, streaming uploads, error categorization, bounded bodies, explicit credentials, redacted diagnostics, and cautious retry behavior. Those decisions leave more room for the part of the service that is actually unique.&lt;/p&gt;

&lt;p&gt;To get started, install the package, work through the runnable examples, and consult the API coverage and security notes before enabling mutations in production. The repository is released under the permissive MIT license, and contributions are welcome. &lt;a href="https://github.com/tigusigalpa/goldsky-go/blob/main/docs/api-coverage.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;github.com/tigusigalpa/goldsky-go&lt;/a&gt;&lt;strong&gt;Package reference:&lt;/strong&gt; &lt;a href="https://pkg.go.dev/github.com/tigusigalpa/goldsky-go" rel="noopener noreferrer"&gt;pkg.go.dev/github.com/tigusigalpa/goldsky-go&lt;/a&gt;&lt;strong&gt;Goldsky API documentation:&lt;/strong&gt; &lt;a href="https://docs.goldsky.com/api-reference/overview" rel="noopener noreferrer"&gt;docs.goldsky.com/api-reference/overview&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>go</category>
      <category>goldsky</category>
      <category>trading</category>
      <category>sdk</category>
    </item>
    <item>
      <title>A Production-Minded Go SDK for Social Media Workflows: Introducing socialkit-go</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Sun, 06 Sep 2026 06:31:20 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/a-production-minded-go-sdk-for-social-media-workflows-introducing-socialkit-go-13ha</link>
      <guid>https://dev.to/tigusigalpa/a-production-minded-go-sdk-for-social-media-workflows-introducing-socialkit-go-13ha</guid>
      <description>&lt;p&gt;Social-content integrations often begin with a deceptively small requirement: fetch a transcript, inspect a creator profile, or put engagement figures on a dashboard. The first HTTP request may be easy. The engineering burden arrives later, when an application needs timeouts, cancellation, pagination, rate-limit visibility, secure handling of access keys, retry discipline, and a sensible response to a job that finishes asynchronously.&lt;/p&gt;

&lt;p&gt;That is the gap &lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;&lt;code&gt;socialkit-go&lt;/code&gt;&lt;/a&gt; is designed to close. It is a typed Go SDK for the SocialKit REST API, giving Go services a single client surface for social-media and video workflows. Instead of spreading hand-written request construction and JSON decoding throughout an application, a developer calls a service on a client and receives typed data, response metadata, and an idiomatic error value. The repository targets Go 1.21+ and uses only the standard library, which keeps adoption pleasantly lightweight.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The useful abstraction is not only “an API wrapper.”&lt;/strong&gt; It is a Go-shaped boundary where request cancellation, response context, operational metadata, and failure semantics are part of the normal call path.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SocialKit itself offers a unified API layer for extracting social-content data such as transcripts, summaries, comments, engagement metrics, profiles, posts, search results, and downloads across YouTube, TikTok, Instagram, Facebook, X/Twitter, LinkedIn, and direct video files.&lt;a href="https://docs.socialkit.dev/" rel="noopener noreferrer"&gt;2&lt;/a&gt; &lt;code&gt;socialkit-go&lt;/code&gt; brings that surface into a package that feels at home in a Go codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the SDK puts behind one client
&lt;/h2&gt;

&lt;p&gt;The package exposes a &lt;code&gt;Client&lt;/code&gt; with dedicated service fields rather than a one-size-fits-all &lt;code&gt;Do&lt;/code&gt; function. This makes intent visible at the call site: &lt;code&gt;client.YouTube&lt;/code&gt;, &lt;code&gt;client.TikTok&lt;/code&gt;, or &lt;code&gt;client.Downloads&lt;/code&gt; says more about the workflow than an endpoint string assembled in business logic. The repository currently maps the following service areas to the underlying API.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service area&lt;/th&gt;
&lt;th&gt;Examples of supported work&lt;/th&gt;
&lt;th&gt;Why it matters in an application&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;YouTube&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transcripts, summaries, stats, comments, channel stats, search, videos, downloads, and experimental bulk operations&lt;/td&gt;
&lt;td&gt;Useful for research products, content intelligence, and creator analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TikTok and Instagram&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transcripts, summaries, stats, comments, channel data, search or reels workflows, and downloads&lt;/td&gt;
&lt;td&gt;Helps normalize short-form content flows without platform-specific request plumbing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Facebook, X/Twitter, and LinkedIn&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Video and content analysis, profile/company data, posts, threads, tweets, and related metadata depending on the platform&lt;/td&gt;
&lt;td&gt;Enables wider social context around a campaign, creator, or organization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direct video&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transcript and summary requests for video-file URLs&lt;/td&gt;
&lt;td&gt;Keeps uploaded or externally hosted video in the same workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Status, Credits, and Downloads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Service availability, account-credit information, async download jobs and polling&lt;/td&gt;
&lt;td&gt;Lets an application account for operational state instead of treating every request as fire-and-forget&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This coverage is especially attractive when a team is building a SaaS feature, internal research tool, or AI-assisted workflow that needs data from several platforms. The alternative is usually a growing collection of platform-specific integrations that differ in request formats, response shapes, and edge cases. SocialKit describes its API as a way to avoid maintaining separate scraping infrastructure while returning structured, developer-oriented JSON.&lt;a href="https://www.socialkit.dev/" rel="noopener noreferrer"&gt;3&lt;/a&gt; The Go SDK does not change the external service’s capabilities; it makes the integration contract clearer and more consistent inside Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a small, observable integration
&lt;/h2&gt;

&lt;p&gt;Installation is the familiar Go command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/tigusigalpa/socialkit-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK’s runnable examples expect an access key in &lt;code&gt;SOCIALKIT_ACCESS_KEY&lt;/code&gt;, not embedded in source code.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; That is a good default for local development and deployment environments alike. The official authentication guide also recommends sending the key through the &lt;code&gt;x-access-key&lt;/code&gt; header and cautions against placing credentials in public repositories, client-side code, logs, or screenshots.&lt;a href="https://docs.socialkit.dev/authentication" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a compact transcript request that has the operational basics already in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="n"&gt;socialkit&lt;/span&gt; &lt;span class="s"&gt;"github.com/tigusigalpa/socialkit-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SOCIALKIT_ACCESS_KEY"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUserAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"research-worker/1.0"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YouTube&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FetchRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://www.youtube.com/watch?v=YOUR_VIDEO_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreditsUsed&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"credits used: %.0f&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreditsUsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RateLimitRemaining&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rate-limit remaining: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RateLimitRemaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Several small decisions in this example carry real production value. Every SDK request accepts a &lt;code&gt;context.Context&lt;/code&gt;, so a worker shutdown, an HTTP request deadline, or a user cancellation can flow through naturally.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; The response is not just content: the companion &lt;code&gt;ResponseMeta&lt;/code&gt; exposes available credit and rate-limit headers, so a product can instrument usage or make informed decisions before blindly issuing more work.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The call also demonstrates why an SDK can be more useful than a copied &lt;code&gt;curl&lt;/code&gt; command. Your application code stays focused on the workflow—“obtain a transcript, then use it”—rather than on repeated HTTP mechanics. The same client pattern applies when a product moves from a YouTube prototype to a TikTok search, an Instagram reels workflow, a LinkedIn company lookup, or a direct video-file summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed operations without hiding the details you need
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;socialkit-go&lt;/code&gt; supplies request and response models for the services it wraps. That helps make optional fields, cursors, limits, and platform-specific inputs visible during implementation. For example, a YouTube summary can be prompted toward a particular perspective, while the response can preserve custom fields in an &lt;code&gt;Extra&lt;/code&gt; map. The latter design gives teams a forward-compatible route for custom-response payloads without forcing all future fields into a static struct immediately.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The service map is intentionally explicit, but it does not prevent developers from seeing important API context. Each successful call returns metadata; errors retain typed information; and options such as &lt;code&gt;WithBaseURL&lt;/code&gt;, &lt;code&gt;WithHTTPClient&lt;/code&gt;, &lt;code&gt;WithTimeout&lt;/code&gt;, and &lt;code&gt;WithUserAgent&lt;/code&gt; leave room for test environments, custom transports, observability, or company-wide client policies.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For teams processing comment feeds, pagination is not a hidden implementation detail. Cursor-based responses expose &lt;code&gt;Cursor&lt;/code&gt; and &lt;code&gt;HasMore&lt;/code&gt;, which allows the calling service to decide when the next page is worth fetching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TikTok&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Comments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommentsRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://www.tiktok.com/@creator/video/VIDEO_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasMore&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasMore&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cursor&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TikTok&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Comments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommentsRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"https://www.tiktok.com/@creator/video/VIDEO_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Cursor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="c"&gt;// merge or process the next page in your application layer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separation is deliberate. The SDK handles the contract, while your application retains control over quotas, storage, deduplication, cancellation, and the business decision to continue paginating.&lt;/p&gt;

&lt;h2&gt;
  
  
  A more defensive path from API response to production behavior
&lt;/h2&gt;

&lt;p&gt;Feature lists are easy to promote. The operational details are the stronger reason to look at this library.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Production concern&lt;/th&gt;
&lt;th&gt;How &lt;code&gt;socialkit-go&lt;/code&gt; addresses it&lt;/th&gt;
&lt;th&gt;Practical implication&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Credential exposure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The client sends the access key as &lt;code&gt;x-access-key&lt;/code&gt; by default, and the repository documents automatic redaction of credential values in error output, metadata, and raw bodies.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Reduces the chance that a diagnostic path turns an incident into a secret leak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deadlines and cancellation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every request accepts &lt;code&gt;context.Context&lt;/code&gt;.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Workers and HTTP handlers can stop waiting when the caller no longer needs the result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transient failures&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Retries are opt-in through &lt;code&gt;WithRetry&lt;/code&gt;; the SDK retries 429 and 5xx responses with exponential backoff, jitter, and &lt;code&gt;Retry-After&lt;/code&gt; handling.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;A temporary upstream problem does not automatically become an application error, while retry policy remains explicit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-transient failures&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP 400, 401, 403, and 404 are not retried.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Avoids masking invalid requests or repeating work that cannot succeed without a change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sentinel errors and typed API errors support Go’s &lt;code&gt;errors.Is&lt;/code&gt; and &lt;code&gt;errors.As&lt;/code&gt; patterns.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Calling code can branch on stable semantics rather than brittle error strings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Usage awareness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Response metadata can expose credits used, credits remaining, and rate-limit information.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Products can meter, alert, or provide useful UX around consumption&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One nuance deserves emphasis: retries are not enabled by default. The repository explicitly warns that a repeated POST can be billable and that retrying should be enabled only for operations a team is comfortable repeating.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; That is a mature trade-off. Reliability should not mean silently multiplying cost or repeating a workflow that a caller intended to run once.&lt;/p&gt;

&lt;p&gt;Typed errors let application code make this policy visible. A request can distinguish unauthorized access, insufficient credits, a rate limit, a missing resource, and an API error with an HTTP status and error code. In other words, the error value can be treated as a decision surface, not merely as a string to log.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YouTube&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"check the SocialKit access key: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrInsufficientCredits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"usage budget exhausted: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrRateLimited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"slow down and retry later: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIError&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"social API returned HTTP %d: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example assumes the usual &lt;code&gt;errors&lt;/code&gt; and &lt;code&gt;fmt&lt;/code&gt; imports. More importantly, it illustrates a clean boundary: the integration layer translates a known external condition, and the rest of the application can react with an appropriate message, queue policy, or fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat downloads as jobs, not as long requests
&lt;/h2&gt;

&lt;p&gt;Media operations can take longer than a normal request-response interaction. For that reason, the SDK provides a v2 asynchronous download service with &lt;code&gt;Start&lt;/code&gt;, &lt;code&gt;Get&lt;/code&gt;, and &lt;code&gt;Wait&lt;/code&gt;. Jobs move through &lt;code&gt;queued&lt;/code&gt;, &lt;code&gt;processing&lt;/code&gt;, &lt;code&gt;ready&lt;/code&gt;, and &lt;code&gt;failed&lt;/code&gt; states; &lt;code&gt;Wait&lt;/code&gt; polls with caller-controlled interval and backoff while respecting the supplied context.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Downloads&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"youtube"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V2DownloadRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"https://youtube.com/watch?v=YOUR_VIDEO_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Quality&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"720p"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;finalJob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Downloads&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;socialkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Interval&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MaxAttempts&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;BackoffFactor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finalJob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a much better fit for workers and background pipelines than pretending a download is always instantaneous. If a job fails, the SDK exposes a dedicated asynchronous-job error that can carry a retryability signal, enabling a queue consumer to decide whether to retry, surface a failure, or request human review.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits well
&lt;/h2&gt;

&lt;p&gt;The most compelling use cases are workflows where social data must become a dependable input to another system. An AI research assistant can combine a transcript, comments, and channel information into a grounded brief. A marketing intelligence dashboard can keep platform-specific data retrieval outside of its presentation logic. A content-repurposing pipeline can turn authorized source videos into transcripts, summaries, and reviewable editorial inputs. An internal tool can fetch creator, company, or engagement context just when a campaign team needs it.&lt;/p&gt;

&lt;p&gt;The common theme is not “collect everything.” It is &lt;strong&gt;build a deliberate workflow around data you are authorized to process&lt;/strong&gt;. The repository’s examples use public placeholders and explicitly remind users to substitute only URLs they are authorized to handle before making billable requests.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; That is the right operational posture: confirm your rights to the content, follow the relevant platform terms and applicable law, and use explicit limits and retention policies in the application you build.&lt;/p&gt;

&lt;h2&gt;
  
  
  A straightforward next step
&lt;/h2&gt;

&lt;p&gt;If your Go application needs SocialKit data, start with one narrow end-to-end feature: a transcript endpoint feeding an internal search index, a creator-analysis view, or a supervised content workflow. Add a context deadline, record the metadata your product needs, and make retry behavior a conscious choice. Once that is in place, the same client structure can grow with your product rather than becoming another isolated HTTP integration.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;socialkit-go&lt;/code&gt; is available under the MIT license, includes runnable examples for basic usage, pagination, async downloads, and error patterns, and has tests that use Go’s &lt;code&gt;httptest&lt;/code&gt; package without requiring a network connection or a real API key.&lt;a href="https://github.com/tigusigalpa/socialkit-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; For developers who want a typed and production-aware Go entry point to SocialKit, that makes the library a practical place to begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>go</category>
      <category>socialkit</category>
      <category>youtube</category>
      <category>instagram</category>
    </item>
    <item>
      <title>Meet Watchdog: A Small, Safe Bash Watchdog for Linux Services</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Wed, 26 Aug 2026 21:47:58 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/meet-watchdog-a-small-safe-bash-watchdog-for-linux-services-hl3</link>
      <guid>https://dev.to/tigusigalpa/meet-watchdog-a-small-safe-bash-watchdog-for-linux-services-hl3</guid>
      <description>&lt;p&gt;A service does not have to be large to become operationally important. A small API behind a reverse proxy, a background worker on a virtual machine, or a database listener used by a side project can all fail at inconvenient moments. In many of those environments, the missing piece is not a sprawling observability program. It is a reliable way to answer a narrower question: &lt;strong&gt;is the service healthy right now, and what should this host do if it is not?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tigusigalpa/watchdog" rel="noopener noreferrer"&gt;Watchdog&lt;/a&gt; is an open-source, dependency-light Bash program built for exactly that job. It checks websites and services from a YAML configuration and can run a defined remediation sequence after a target remains unavailable through its configured retries. It is deliberately a &lt;strong&gt;one-shot&lt;/strong&gt; tool: invoke it from a &lt;code&gt;systemd&lt;/code&gt; timer, cron, or an existing scheduler instead of keeping another permanent daemon alive. The project is MIT-licensed and targets Linux systems with Bash 4.3 or newer. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That design is compelling when you own the host, understand the recovery action, and want the behavior to stay visible in a versioned configuration file. Watchdog is not trying to replace metrics, distributed tracing, external uptime checks, or a full incident-management platform. It is a focused, host-level building block for detection, bounded recovery, and signal-rich notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  One configuration, three useful health signals
&lt;/h2&gt;

&lt;p&gt;Different outages need different evidence. An HTTP success code says something different from an open TCP socket, and a local process manager may already expose the most authoritative health signal. Watchdog supports all three common forms of check, with shared timeout, retry, and retry-delay controls. It stops retrying as soon as one attempt succeeds; only an exhausted sequence is treated as unavailable. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Check-Types" rel="noopener noreferrer"&gt;3&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check type&lt;/th&gt;
&lt;th&gt;What it validates&lt;/th&gt;
&lt;th&gt;Strong fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP/HTTPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A request completes and returns an accepted status code; by default, the final status must be in the 2xx range.&lt;/td&gt;
&lt;td&gt;Public APIs, web apps, reverse proxies, and &lt;code&gt;/health&lt;/code&gt; or &lt;code&gt;/readyz&lt;/code&gt; endpoints.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A connection to &lt;code&gt;host:port&lt;/code&gt; opens before the configured timeout.&lt;/td&gt;
&lt;td&gt;Databases, caches, brokers, and other services that must accept connections.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Command&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every configured local command exits successfully, in order.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;systemd&lt;/code&gt;-managed workers, queue probes, or domain-specific local checks.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The distinction matters. A TCP connection is inexpensive and useful, but it does not prove that an application can authenticate, process a query, or serve real traffic. For a public service, an HTTP readiness endpoint is often the better customer-facing signal. For a local worker, &lt;code&gt;systemctl is-active --quiet&lt;/code&gt; or a domain-specific probe can be more meaningful. Watchdog lets each service use the check that best represents its actual definition of healthy. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Check-Types" rel="noopener noreferrer"&gt;3&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Safe automation starts with how commands are represented
&lt;/h2&gt;

&lt;p&gt;The feature that deserves special attention is not merely that Watchdog can restart something. It is &lt;strong&gt;how&lt;/strong&gt; it runs the command. Configured commands are YAML argument arrays rather than shell strings, and the implementation invokes them directly rather than interpreting a configured command through &lt;code&gt;eval&lt;/code&gt; or &lt;code&gt;bash -c&lt;/code&gt;. This preserves argument boundaries and avoids turning routine configuration text into accidental shell syntax. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In practical terms, a remediation action is declared as an executable plus its arguments—not as a fragment of shell code to be re-parsed later.&lt;/strong&gt; That makes configuration easier to review and reduces a class of quoting and interpolation mistakes in operational workflows. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each command can also have an explicit timeout and, when needed, a working directory. Remediation commands run in order and stop at the first non-zero result. That behavior makes the sequence readable: the YAML describes exactly which corrective steps the host will try, in what order, and for how long. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Check-Types" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  From failed check to verified recovery
&lt;/h2&gt;

&lt;p&gt;Good automation should be deliberate, especially when the corrective action is a restart. Watchdog separates detection from remediation. A service can retry its check before it is declared unavailable; then, if commands are configured and the per-service cooldown permits it, Watchdog executes the ordered remediation sequence. After an optional &lt;code&gt;verify_after&lt;/code&gt; delay, it runs the full health check again rather than assuming that a successful restart command automatically means the service recovered. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Remediation%2C-State%2C-and-Exit-Codes" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project also persists per-service state. This allows notifications and hooks to be transition-aware: one failure event appears when a service moves from &lt;code&gt;unknown&lt;/code&gt; or &lt;code&gt;healthy&lt;/code&gt; to &lt;code&gt;unavailable&lt;/code&gt;, repeated runs during the same ongoing outage do not produce duplicate failure events, and a later return to health produces a recovery event. A continuing outage may still receive a later remediation attempt after its cooldown, but it does not flood operators with the same alert every scheduler interval. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Notifications-and-Hooks" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Why it is useful in production&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;attempts&lt;/code&gt;&lt;/strong&gt;** + *&lt;em&gt;**&lt;code&gt;retry_delay&lt;/code&gt;&lt;/em&gt;*&lt;/td&gt;
&lt;td&gt;Filters short-lived network or startup blips before marking a service unavailable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;cooldown&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sets a minimum interval between remediation attempts for one service, helping to avoid restart loops.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;verify_after&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Leaves time for a restart to settle, then verifies the service with the actual health check.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Persistent state&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Makes failure and recovery actions transition-based instead of repetitive.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-blocking global lock&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prevents overlapping scheduled invocations when a previous run is still active.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a pragmatic balance for small operations: the configuration does not pretend an outage is solved just because a command returned zero, and the alerting model does not confuse persistence with importance. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Notifications-and-Hooks" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical Docker Compose example
&lt;/h2&gt;

&lt;p&gt;Imagine a public API whose health endpoint should return either &lt;code&gt;200&lt;/code&gt; or &lt;code&gt;204&lt;/code&gt;. The service runs in Docker Compose, and a restart should be attempted only after two failed checks. The following pattern comes directly from Watchdog’s documented examples, with values that you should replace for your own host. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Examples-and-Recipes" rel="noopener noreferrer"&gt;6&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
    &lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.example.com/health&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
      &lt;span class="na"&gt;follow_redirects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;success_status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;200&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;204&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;attempts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
      &lt;span class="na"&gt;retry_delay&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;

    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;cooldown&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
      &lt;span class="na"&gt;verify_after&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;compose&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;working_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv/example-api&lt;/span&gt;
          &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;compose&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;working_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv/example-api&lt;/span&gt;
          &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operational logic is easy to read. First, test the endpoint that users or upstream systems rely on. If the check still fails after the retry policy, attempt a controlled restart from the directory containing the Compose file. Wait five seconds, then check the endpoint again. The cooldown means this should not become a restart-on-every-minute loop during a prolonged dependency failure. For a PostgreSQL listener, a Redis instance, or a message broker, the same model can use a TCP check. For a systemd worker, it can use a command check and an explicit &lt;code&gt;systemctl restart&lt;/code&gt; action. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Examples-and-Recipes" rel="noopener noreferrer"&gt;3&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Notifications that tell you what changed
&lt;/h2&gt;

&lt;p&gt;Watchdog includes built-in SMTP email and can also execute local failure and recovery hooks. Both mechanisms are transition-based. That lets a small team receive an alert when an incident begins, a recovery message when availability returns, and no redundant notification every time the scheduler observes the same unresolved fault. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Notifications-and-Hooks" rel="noopener noreferrer"&gt;5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For SMTP, the documented configuration supports templated subjects and bodies as well as an environment-variable password field. With the packaged systemd service, secrets can be kept in a root-owned environment file rather than committed in YAML. For integrations that belong outside the main configuration, hooks expose contextual environment variables such as the service name, event, check type, diagnostic detail, HTTP status, and timestamp. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Notifications-and-Hooks" rel="noopener noreferrer"&gt;5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach stays intentionally modest. Instead of embedding every chat or incident-management provider into the watchdog itself, the tool can hand a clean, bounded event to a local wrapper that follows your team’s preferred integration path. That is a useful boundary for a Bash utility: keep the health and remediation engine predictable, while allowing local automation to adapt it to the surrounding environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start conservatively, then schedule it
&lt;/h2&gt;

&lt;p&gt;Watchdog’s recommended workflow is refreshingly operational. Configure one target, validate the configuration in dry-run mode, test a controlled failure and recovery on a non-production service, and only then enable the schedule. The &lt;code&gt;-n&lt;/code&gt; flag still validates the configuration and runs health checks, but it skips state changes, remediation, hooks, and transition notifications. The &lt;code&gt;-s&lt;/code&gt; option narrows a run to a single configured service, which makes first tests safer and easier to interpret. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Examples-and-Recipes" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Validate one service without changing state or restarting anything.&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; /opt/service-watchdog/service-watchdog.sh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; /etc/service-watchdog/config.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-s&lt;/span&gt; api &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt;

&lt;span class="c"&gt;# Once validated, enable the packaged systemd timer.&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; service-watchdog.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository ships a &lt;code&gt;systemd&lt;/code&gt; oneshot service and timer, while cron is also supported when it is the established deployment standard. The packaged timer is configured to run every minute and uses a persistent timer; the paired unit treats Watchdog’s ordinary service-outage/remediation exit result as expected while preserving configuration or environment errors as failures that require investigation. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Scheduling-with-systemd-and-cron" rel="noopener noreferrer"&gt;7&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before setting an interval, take a realistic look at the worst-case time spent in checks, retries, corrective commands, and verification. Watchdog has a non-blocking lock, so it will not overlap invocations; however, repeatedly skipped runs are still a useful signal that cadence or timeout settings need revision. &lt;a href="https://github.com/tigusigalpa/watchdog/wiki/Scheduling-with-systemd-and-cron" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A small tool with a clear operational contract
&lt;/h2&gt;

&lt;p&gt;There is real value in infrastructure tools that have a narrow purpose and state their boundaries clearly. Watchdog gives Linux operators a compact contract: declare health checks in YAML, decide the recovery commands in advance, use retries and cooldowns to keep the actions bounded, verify recovery, and receive notification only when availability actually changes.&lt;/p&gt;

&lt;p&gt;If you operate a Docker Compose application, a systemd-managed worker, a local database listener, or a small fleet of Linux services, that can be the right amount of automation. Explore the &lt;a href="https://github.com/tigusigalpa/watchdog" rel="noopener noreferrer"&gt;repository&lt;/a&gt;, read the &lt;a href="https://github.com/tigusigalpa/watchdog/wiki" rel="noopener noreferrer"&gt;Wiki&lt;/a&gt;, and begin with the ready-to-adapt &lt;a href="https://github.com/tigusigalpa/watchdog/tree/main/examples" rel="noopener noreferrer"&gt;examples&lt;/a&gt;. Test against a controlled, non-production target first, then make Watchdog part of a deliberate host-level reliability routine.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>monitoring</category>
      <category>bash</category>
    </item>
    <item>
      <title>Stop Hand-Rolling Your Arkham API Client: Meet arkham-go</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Wed, 19 Aug 2026 10:33:35 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/stop-hand-rolling-your-arkham-api-client-meet-arkham-go-3598</link>
      <guid>https://dev.to/tigusigalpa/stop-hand-rolling-your-arkham-api-client-meet-arkham-go-3598</guid>
      <description>&lt;p&gt;Working with on-chain intelligence is rarely difficult because of a single HTTP request. The hard part is everything that surrounds the request: modelling response payloads, handling deadlines, preserving decimal values, applying pagination correctly, reacting to rate limits, keeping an eye on API-credit consumption, and maintaining a separate real-time connection when the product needs live transfer data.&lt;/p&gt;

&lt;p&gt;That operational layer is where projects tend to accumulate fragile helpers and repeated boilerplate. A handler builds a URL in one package, a retry loop appears in another, and a stream connection is managed somewhere else entirely. The result may work in development, but it becomes difficult to reason about under load, during a partial outage, or when the API evolves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;&lt;code&gt;arkham-go&lt;/code&gt;&lt;/a&gt; is designed to remove that friction. It is a production-ready Go SDK for the Arkham Intel API, built around idiomatic Go structures and the standard library. The library covers the documented REST surface and WebSocket v2 transfer streams, giving Go teams a structured foundation for applications that use address, entity, transfer, market, portfolio, risk, and related on-chain intelligence. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“It handles the REST endpoints and WebSocket v2 streams for you, so you can focus on building with on-chain intelligence instead of wrestling with HTTP plumbing.” — the project README &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why an SDK matters here
&lt;/h2&gt;

&lt;p&gt;The Arkham API guide highlights the integration concerns that appear in real applications: authentication, rate limits, credit pricing, pagination, and a data model spanning addresses, entities, labels, and tags. &lt;a href="https://arkm.com/api/docs" rel="noopener noreferrer"&gt;2&lt;/a&gt; Those are not incidental details. They affect how reliably an integration behaves and how easily its costs and failure modes can be understood.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arkham-go&lt;/code&gt; makes those concerns part of the client contract rather than leaving every application to implement them independently. It does not promise to decide what your product should do with on-chain data; it gives your Go code predictable, typed primitives for retrieving and operationally managing that data.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Integration challenge&lt;/th&gt;
&lt;th&gt;What &lt;code&gt;arkham-go&lt;/code&gt; provides&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request and response decoding&lt;/td&gt;
&lt;td&gt;Strongly typed Go structs for documented requests and responses. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cancellation and deadlines&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;context.Context&lt;/code&gt; argument on every network method. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Temporary API failures&lt;/td&gt;
&lt;td&gt;Exponential backoff with jitter for &lt;code&gt;429&lt;/code&gt; and &lt;code&gt;5xx&lt;/code&gt; responses, including support for &lt;code&gt;Retry-After&lt;/code&gt;. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error classification&lt;/td&gt;
&lt;td&gt;Sentinel errors such as &lt;code&gt;ErrBadRequest&lt;/code&gt;, &lt;code&gt;ErrUnauthorized&lt;/code&gt;, and &lt;code&gt;ErrRateLimited&lt;/code&gt;, compatible with &lt;code&gt;errors.Is&lt;/code&gt; and &lt;code&gt;errors.As&lt;/code&gt;. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usage observability&lt;/td&gt;
&lt;td&gt;Response metadata carrying HTTP details and &lt;code&gt;X-Intel-Datapoints-*&lt;/code&gt; headers. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large result sets&lt;/td&gt;
&lt;td&gt;Caller-controlled offset pagination with explicit limits for items and pages. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live transfer workflows&lt;/td&gt;
&lt;td&gt;WebSocket v2 stream creation, connection, reception, reconnection, and deletion helpers. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a pragmatic design choice. An SDK becomes more valuable when the network is less predictable: a timeout must stop background work, a rate limit must be recognised as a distinct condition, and a delayed response must not turn into unbounded retry traffic. By making these behaviours visible in the API, &lt;code&gt;arkham-go&lt;/code&gt; helps keep application-level code focused on business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started with a small surface area
&lt;/h2&gt;

&lt;p&gt;The project targets Go 1.21 or newer and requires an Arkham API key. Installation is a normal Go module command: &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/tigusigalpa/arkham-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the key in an environment variable rather than committing it to source control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ARKHAM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A minimal client can be created either from the key directly or with &lt;code&gt;NewClientFromEnv()&lt;/code&gt; when the conventional &lt;code&gt;ARKHAM_API_KEY&lt;/code&gt; environment variable is present. The following example looks up intelligence for an address and reads the returned usage metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="n"&gt;arkham&lt;/span&gt; &lt;span class="s"&gt;"github.com/tigusigalpa/arkham-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ARKHAM_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intelligence&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s"&gt;"0x28C6c06298d514Db089934071355E5743bf21d60"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArkhamEntity&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Entity:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArkhamEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Usage: %s/%s datapoints&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntelDatapointsUsage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntelDatapointsLimit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is not simply that the example is short. It is that the return values make an important operational signal available right next to the decoded domain value. A service call returns &lt;code&gt;*arkham.ResponseMetadata&lt;/code&gt; alongside its result, including status information, request duration, final URL, and the relevant datapoint headers. That makes it possible to add usage logging or alerting without manually parsing headers throughout the codebase. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build with types, not scattered query strings
&lt;/h2&gt;

&lt;p&gt;One of the most welcome qualities of Go is that its types make incorrect assumptions harder to hide. &lt;code&gt;arkham-go&lt;/code&gt; carries that preference into its filter and service interfaces. For transfer queries, typed option structs are converted into query parameters by the SDK. Decimal fields such as a USD threshold are represented as strings, preserving the decimal form expected by the API. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TransferFilter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"binance"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Chains&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"ethereum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bitcoin"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Flow&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FlowOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UsdGte&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"100000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SortKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SortKeyTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SortDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SortDirDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TimeRange&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeRange&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TimeLast&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"24h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;transfers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transfers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transfers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Datapoints remaining:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntelDatapointsRemaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This style has a useful maintenance benefit: intent is obvious at the call site. A reviewer can see the requested chains, flow direction, time range, and sorting policy without having to reconstruct a URL. The library also validates relevant options; for example, the documented transfer filter rules prevent mixing &lt;code&gt;TimeLast&lt;/code&gt; with &lt;code&gt;TimeGte&lt;/code&gt; or &lt;code&gt;TimeLte&lt;/code&gt;. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The service surface is broad enough to keep related capabilities inside one client. According to the project documentation, the client exposes services for intelligence, balances, chains, counterparties, historical flows and balances, loans, market data, portfolio snapshots, risk, swaps, tokens, transfers, transactions, users, subscriptions, analytics, and WebSocket streams, among other areas. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; Whether you are building a monitoring workflow, analytics pipeline, research tool, or internal operations dashboard, that unified structure reduces the need to invent a different integration pattern for every endpoint family.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability should be the default
&lt;/h2&gt;

&lt;p&gt;A thin API wrapper can send requests. A production-oriented SDK should also help callers deal with the response when it is not the one they wanted.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arkham-go&lt;/code&gt; accepts functional options for the base URL, timeout, retry count, retry delay, user agent, HTTP client, and logger. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; This lets teams begin with a concise default client and move toward explicit operational controls as an integration grows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMaxRetries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithBaseDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUserAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"wallet-monitor/1.0"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The retry policy is intentionally selective. The README documents automatic retries for &lt;code&gt;429&lt;/code&gt; and &lt;code&gt;5xx&lt;/code&gt; responses on &lt;code&gt;GET&lt;/code&gt; requests, with &lt;code&gt;Retry-After&lt;/code&gt; honoured when supplied. Mutating requests are not retried unless the SDK knows they are safe to repeat. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; That distinction respects a basic reliability principle: retrying should improve resilience without quietly multiplying side effects.&lt;/p&gt;

&lt;p&gt;Errors are similarly designed for normal Go control flow. Instead of forcing callers to compare incidental error strings, the library offers sentinel errors and a structured &lt;code&gt;APIError&lt;/code&gt;. The familiar &lt;code&gt;errors.Is&lt;/code&gt; and &lt;code&gt;errors.As&lt;/code&gt; pattern remains available.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intelligence&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"0xabc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrRateLimited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Queue the work, apply a policy, or notify your scheduler.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIError&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"API status=%d retry_after=%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetryAfter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK also wraps transport failures and cancelled contexts in a way that preserves standard error inspection. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; This matters because application-specific behaviour—such as telling a job runner to retry later or showing a clear error in an operator console—belongs in the application, but it depends on receiving dependable signals from the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pagination without surprises
&lt;/h2&gt;

&lt;p&gt;Offset pagination is easy to make look correct while silently requesting more data than a job can safely process. &lt;code&gt;arkham-go&lt;/code&gt; exposes a &lt;code&gt;Paginator&lt;/code&gt; for list endpoints that fetches one page at a time and lets the caller cap both the total item count and the number of page requests. It deliberately does not guess that a remote list is exhausted; callers stop when a decoded page is empty. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is a refreshingly explicit contract. It makes resource limits visible in code and prevents hidden work from growing without bound.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPaginator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"/transfers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// page size&lt;/span&gt;
    &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// maximum items requested; 0 is unlimited&lt;/span&gt;
    &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c"&gt;// maximum requests; 0 is unlimited&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasNext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NextPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// Process the current page before asking for the next one.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For ETL-style tasks, backfills, and scheduled monitoring, this kind of control is more useful than a convenience abstraction that hides where the next API request will come from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time transfer streams, without a separate client
&lt;/h2&gt;

&lt;p&gt;Some workflows cannot wait for the next polling interval. A monitoring service may need to react to qualifying transfers as they arrive. &lt;code&gt;arkham-go&lt;/code&gt; includes WebSocket v2 stream management alongside its REST services: create a stream, connect to it, receive messages, reconnect if necessary, and delete the stream when the work is complete. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;arkham&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateStreamV2Request&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"binance"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;UsdGte&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"500000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StreamID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StreamID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReceiveTyped&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The README documents reconnect support for interrupted connections within the API reactivation window, and it also notes that unused streams should be deleted. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; Encapsulating this lifecycle within the same SDK is valuable: the real-time path remains consistent with the same authentication, error, context, and configuration conventions used by ordinary client calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  A clean dependency story
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;arkham-go&lt;/code&gt; has &lt;strong&gt;no runtime dependencies outside the Go standard library&lt;/strong&gt;. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; For many teams, that is not a philosophical talking point; it is an operational advantage. Fewer dependencies can mean a smaller dependency review surface, simpler vendoring and builds, and less uncertainty when integrating the SDK into a service with strict deployment requirements.&lt;/p&gt;

&lt;p&gt;The project is released under the &lt;strong&gt;MIT License&lt;/strong&gt;, which makes it straightforward to evaluate and adopt in a wide range of codebases. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; Its repository also includes runnable examples for intelligence, transfers, pagination, and WebSocket workflows, plus standard &lt;code&gt;go test ./...&lt;/code&gt; and &lt;code&gt;go vet ./...&lt;/code&gt; commands for contributors and evaluators. &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;If your Go application needs Arkham API capabilities, begin by cloning the smallest possible integration: create a client, pass a context with a deadline, make one typed service call, and record the response metadata. From there, use typed filters as query needs grow, introduce bounded pagination for batch work, and move to a WebSocket v2 stream when the use case is genuinely real-time.&lt;/p&gt;

&lt;p&gt;The value of &lt;code&gt;arkham-go&lt;/code&gt; is not that it makes every integration decision for you. Its value is that it packages the repeatable, error-prone client work—request plumbing, typed models, retries, errors, pagination, usage metadata, and streams—into a Go-native interface. That leaves you with more time to build the part your users actually notice.&lt;/p&gt;

&lt;p&gt;Explore the project, review the examples, and try it in your next Go service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/tigusigalpa/arkham-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/tigusigalpa/arkham-go" rel="noopener noreferrer"&gt;github.com/tigusigalpa/arkham-go&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>go</category>
      <category>arkham</category>
      <category>blockchain</category>
      <category>cryptocurrency</category>
    </item>
    <item>
      <title>Stop Fighting Generated Code: Introducing a Hand-Crafted Go SDK for KuCoin</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Sun, 09 Aug 2026 12:58:39 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/stop-fighting-generated-code-introducing-a-hand-crafted-go-sdk-for-kucoin-4eaj</link>
      <guid>https://dev.to/tigusigalpa/stop-fighting-generated-code-introducing-a-hand-crafted-go-sdk-for-kucoin-4eaj</guid>
      <description>&lt;p&gt;Integrating an exchange API is rarely difficult because an endpoint is missing. It is difficult because the integration becomes part of a system that must behave predictably when the network is slow, when rate limits are reached, when API documentation is ambiguous, and when a number must retain every decimal place. In that environment, convenience wrappers can be expensive: a client that merely exposes every endpoint is not necessarily a client that feels safe to build upon.&lt;/p&gt;

&lt;p&gt;That is the motivation behind &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;&lt;code&gt;kucoin-go&lt;/code&gt;&lt;/a&gt;, an unofficial Go client for KuCoin’s UTA (Unified Trading Account) and Classic API families. The project is built from scratch against KuCoin’s current documentation rather than as a wrapper over the official Universal SDK. Its goal is deliberately narrower: offer a consistent, idiomatic Go experience and expand endpoint coverage method by method, with tests and documentation keeping pace. &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We'd rather ship a small, correct surface than a large, half-tested one.” — &lt;em&gt;kucoin-go&lt;/em&gt; project README &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a claim that generated SDKs have no place. If your application needs comprehensive KuCoin coverage immediately, the official client remains the sensible choice. &lt;code&gt;kucoin-go&lt;/code&gt; is for Go developers who prefer an explicit, thoughtfully designed API surface—and who value being able to see precisely what is implemented today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a smaller SDK can be a better foundation
&lt;/h2&gt;

&lt;p&gt;An exchange client occupies an awkward but important boundary. On one side are remote API shapes, rate limits, account models, and authentication rules. On the other are your Go services, jobs, alerting pipeline, or trading logic. A useful SDK should make that boundary visible rather than hiding it behind generic types and optimistic defaults.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kucoin-go&lt;/code&gt; applies a few clear rules at that boundary. Network calls take a &lt;code&gt;context.Context&lt;/code&gt; as their first argument, letting an application define cancellation and timeout behavior using standard Go mechanisms. The client can also receive an injected &lt;code&gt;*http.Client&lt;/code&gt;, clock, and logger, which makes it easier to apply a house network policy in production and to write controlled tests. &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project keeps KuCoin’s two account models explicit. UTA and Classic expose different permissions, hosts, and response shapes, so the SDK does not flatten them into one misleading abstraction. The current UTA surface is accessed through &lt;code&gt;client.UTA&lt;/code&gt;; the Classic service root is planned for later coverage. This distinction is especially helpful when an application needs to reason about which account model a request actually belongs to. &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Design choice&lt;/th&gt;
&lt;th&gt;What it means in practice&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context-first calls&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every network method begins with &lt;code&gt;context.Context&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Request deadlines and cancellation stay under application control.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Explicit service roots&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;UTA and Classic are modeled as separate domains.&lt;/td&gt;
&lt;td&gt;Account-specific permissions and payload differences are not blurred.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Injected dependencies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP client, clock, logger, and retry policy are configurable.&lt;/td&gt;
&lt;td&gt;Production behavior and testing can be tailored without forking the SDK.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typed transport errors&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Errors work with &lt;code&gt;errors.Is&lt;/code&gt; and &lt;code&gt;errors.As&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Code can branch on meaningful conditions instead of matching strings.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Treat money as data, not as a float
&lt;/h2&gt;

&lt;p&gt;One of the most intentional choices in &lt;code&gt;kucoin-go&lt;/code&gt; is deceptively simple: price, quantity, PnL, and fee fields are represented as &lt;strong&gt;strings&lt;/strong&gt; at the transport boundary. &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That may look less convenient than decoding directly into &lt;code&gt;float64&lt;/code&gt;, but it is the correct friction for financial values. Binary floating-point numbers cannot represent many decimal fractions exactly. If a service receives an exchange value, performs arithmetic, and serializes it again, silent rounding can turn an apparently harmless conversion into a difficult reconciliation problem.&lt;/p&gt;

&lt;p&gt;The library therefore preserves the value exactly as it arrives from the API and asks the application to choose an arithmetic type intentionally—for example, &lt;code&gt;math/big.Rat&lt;/code&gt; or a decimal package. It is a small API design decision with a useful message: precision should be an explicit responsibility, never an accidental by-product of JSON unmarshalling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transport behavior designed for production realities
&lt;/h2&gt;

&lt;p&gt;A clean Go method signature is only half of an SDK. What happens when the server rejects a call, sends a business-level error, or asks the caller to slow down matters just as much.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kucoin-go&lt;/code&gt; uses a shared transport executor that decodes response envelopes and exposes useful metadata, including the HTTP status, KuCoin business code and message, request ID, rate-limit headers, and server timing headers. The SDK’s error hierarchy is designed to be inspected with Go’s native error helpers rather than parsed as text. &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Market&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetTickers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;market&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TradeTypeSpot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"BTC-USDT"&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrRateLimited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Apply the application's backoff strategy.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KucoinError&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its retry policy is conservative by design. It applies exponential backoff with jitter only to &lt;code&gt;GET&lt;/code&gt; requests and bounds the total retry window. Write operations are not automatically retried. That distinction is important: reissuing a data lookup after a transient failure may be safe; reissuing a request that places, cancels, or amends an order can create a duplicate-action problem. The project makes the safer default explicit and leaves idempotency decisions with the application. &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Authentication follows the same philosophy. The library includes an independently implemented HMAC-SHA256 signer for KuCoin authentication headers and validates it with known-answer fixture vectors. The README also recommends obtaining credentials from environment variables or a secret store, using the minimum permission required, and restricting keys by IP where the exchange supports it. &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation that is checked, not merely written
&lt;/h2&gt;

&lt;p&gt;Many SDK repositories start with a good endpoint table and gradually lose the race between code changes and documentation. &lt;code&gt;kucoin-go&lt;/code&gt; tries to remove that failure mode.&lt;/p&gt;

&lt;p&gt;Its endpoint reference is generated from an internal manifest, &lt;code&gt;internal/endpoints.yaml&lt;/code&gt;. Contributors update the manifest, regenerate &lt;code&gt;docs/ENDPOINTS.md&lt;/code&gt;, and CI rejects a change if the generated file differs from the committed version. In addition, each exported method must link to the exact KuCoin documentation page that it implements. &lt;a href="https://github.com/tigusigalpa/kucoin-go/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The practical result is a highly useful question to ask before starting an integration: &lt;strong&gt;does the method exist here today?&lt;/strong&gt; Rather than inferring coverage from a roadmap or package name, a developer can consult a concise list of supported methods, their account mode, HTTP method, permission requirement, test location, and a direct upstream documentation link. &lt;a href="https://github.com/tigusigalpa/kucoin-go/blob/main/docs/ENDPOINTS.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Current scope: the honest version
&lt;/h2&gt;

&lt;p&gt;This is an early, pre-1.0 Phase 1 checkpoint. Today, &lt;code&gt;kucoin-go&lt;/code&gt; implements and tests &lt;strong&gt;UTA Market&lt;/strong&gt; functionality only. It does not yet implement UTA account, order, position, leverage, or transfer operations; Classic Spot, Margin, and Futures coverage; or any WebSocket client. &lt;a href="https://github.com/tigusigalpa/kucoin-go/blob/main/docs/ENDPOINTS.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The current coverage map contains ten UTA Market methods. Most are public read operations, while order-book retrieval is a notable exception: development smoke testing showed that &lt;code&gt;GetOrderBook&lt;/code&gt; requires authentication and the &lt;code&gt;General&lt;/code&gt; permission, despite appearing public alongside similar market-data endpoints. The SDK documents that requirement rather than letting users discover it at runtime. &lt;a href="https://github.com/tigusigalpa/kucoin-go/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Available now&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;GetInstruments&lt;/code&gt;, &lt;code&gt;GetTickers&lt;/code&gt;, &lt;code&gt;GetKlines&lt;/code&gt;, &lt;code&gt;GetTrades&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;UTA market discovery and trading-data retrieval.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;GetCurrencies&lt;/code&gt;, &lt;code&gt;GetCurrency&lt;/code&gt;, &lt;code&gt;GetServiceStatus&lt;/code&gt;, &lt;code&gt;GetAnnouncements&lt;/code&gt;, &lt;code&gt;GetTradeStatistics&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;UTA market and platform information.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GetOrderBook&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Supported, but requires credentials with the &lt;code&gt;General&lt;/code&gt; permission. &lt;a href="https://github.com/tigusigalpa/kucoin-go/blob/main/docs/ENDPOINTS.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSockets, order placement, transfers, Classic APIs&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not implemented yet.&lt;/strong&gt; &lt;a href="https://github.com/tigusigalpa/kucoin-go/blob/main/docs/ENDPOINTS.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That candor is a feature, not a disclaimer hidden at the bottom of the page. A library that makes its limits obvious allows developers to select it appropriately: it is ready to support UTA market-data integration and not ready to run a production order-execution workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start: fetch a ticker without API credentials
&lt;/h2&gt;

&lt;p&gt;Public market-data calls offer the fastest way to evaluate the SDK. The repository requires Go 1.22 or later and installs with the standard command below. &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/tigusigalpa/kucoin-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following example retrieves the latest price for the &lt;code&gt;BTC-USDT&lt;/code&gt; spot symbol. Because this call is public, the client can be created without credentials. &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;

    &lt;span class="n"&gt;kucoin&lt;/span&gt; &lt;span class="s"&gt;"github.com/tigusigalpa/kucoin-go"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/tigusigalpa/kucoin-go/uta/market"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kucoin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTA&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Market&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetTickers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;market&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TradeTypeSpot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"BTC-USDT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastPrice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example captures the intended ergonomics: the account model and domain are visible in the call path, request lifetime is represented by a context, and &lt;code&gt;LastPrice&lt;/code&gt; remains an exact string until the application decides how to process it.&lt;/p&gt;

&lt;h2&gt;
  
  
  An invitation to shape the roadmap
&lt;/h2&gt;

&lt;p&gt;The roadmap is organized around a reliable core first, then broader trading domains, then specialty capabilities such as funding, subaccounts, and additional KuCoin products. Contributions are welcome, particularly for a single missing REST endpoint accompanied by fixtures, tests, a documentation link, and an updated generated coverage entry. &lt;a href="https://github.com/tigusigalpa/kucoin-go/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your project needs full API coverage now, start with the official SDK. If you need a compact Go client for UTA market data and want a codebase that favors clear constraints, typed boundaries, testability, and documentation fidelity, try &lt;code&gt;kucoin-go&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Explore the repository, inspect the coverage map before integrating, and open an issue if the next endpoint on the roadmap is the one your project needs: &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;&lt;strong&gt;github.com/tigusigalpa/kucoin-go&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;kucoin-go&lt;/code&gt; is an unofficial community-maintained client. It is provided as-is and is not financial advice. Review the project’s security notice and test new integrations with least-privilege credentials before using them with funded accounts. &lt;a href="https://github.com/tigusigalpa/kucoin-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>go</category>
      <category>kucoin</category>
      <category>cryptocurrency</category>
      <category>bitcoin</category>
    </item>
    <item>
      <title>Introducing bitget-php: a production-grade PHP and Laravel SDK for Bitget UTA v3</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Sun, 09 Aug 2026 09:21:11 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/introducing-bitget-php-a-production-grade-php-and-laravel-sdk-for-bitget-uta-v3-eh1</link>
      <guid>https://dev.to/tigusigalpa/introducing-bitget-php-a-production-grade-php-and-laravel-sdk-for-bitget-uta-v3-eh1</guid>
      <description>&lt;p&gt;PHP is a strong fit for dashboards, back-office systems, trading utilities, and event-driven services. Yet exchange integrations can still become the most fragile part of the stack. Developers must sign authenticated requests correctly, retain decimal precision, surface actionable API errors, and keep real-time connections healthy when a network or remote service interrupts them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;&lt;strong&gt;bitget-php&lt;/strong&gt;&lt;/a&gt; is an open-source SDK that aims to make that boundary more dependable. It is a production-oriented PHP 8.2+ client for the Bitget Unified Trading Account (UTA) v3 API, with optional Laravel 10–13 integration. The project’s initial release focuses deliberately on a practical core: market data, account operations, trading operations, and a reconnecting WebSocket client. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The library is built for &lt;strong&gt;demo-first trading safety&lt;/strong&gt;. Its own documentation recommends validating new code against Bitget demo credentials before connecting an application to a live account. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article introduces the library from a developer’s perspective: what it covers today, how its design avoids several common integration pitfalls, and how it can become a stable starting point for a PHP-based Bitget integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The exchange-integration problem is more than HTTP
&lt;/h2&gt;

&lt;p&gt;Calling an exchange endpoint may look like a simple &lt;code&gt;POST&lt;/code&gt; or &lt;code&gt;GET&lt;/code&gt;, but a production integration has a much wider responsibility. A client must consistently apply request signing, make error states intelligible to the application, avoid leaking API credentials through logs, and preserve the exact meaning of a decimal value. It also needs an operational model for real-time transport: what happens after a disconnect, and how does an application recover its subscriptions?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bitget-php&lt;/code&gt; addresses these concerns at the SDK layer. It uses Guzzle for HTTP transport and allows a &lt;code&gt;GuzzleHttp\Client&lt;/code&gt; to be injected, which is helpful when testing, routing requests through a proxy, or applying project-specific transport configuration. It supports PSR-3 logging while using a no-op logger by default, and its documentation states that credentials are never logged. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Design concern&lt;/th&gt;
&lt;th&gt;How &lt;code&gt;bitget-php&lt;/code&gt; addresses it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decimal values&lt;/td&gt;
&lt;td&gt;Prices, quantities, PnL, and fee values are represented as strings rather than floats. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API failures&lt;/td&gt;
&lt;td&gt;A typed exception hierarchy exposes specific application-level failure paths and the raw Bitget error code. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP testing and customization&lt;/td&gt;
&lt;td&gt;The Guzzle client is injectable instead of being fixed internally. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;The SDK accepts a PSR-3 logger and defaults to &lt;code&gt;NullLogger&lt;/code&gt;; credentials are not written to logs. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time reliability&lt;/td&gt;
&lt;td&gt;The WebSocket client maintains heartbeat handling, reconnection, and prior subscription recovery. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Framework flexibility&lt;/td&gt;
&lt;td&gt;Laravel support is supplied without making Laravel a requirement for non-Laravel projects. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The approach to decimals deserves special attention. Binary floating-point values cannot precisely represent many decimal fractions, so silently converting exchange quantities or prices into PHP floats can change a value in ways that matter to accounting and order logic. By keeping API-facing numeric values as strings, the SDK leaves exact arithmetic under the developer’s control, including the option to use BCMath or another appropriate decimal strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  A modern PHP foundation without framework lock-in
&lt;/h2&gt;

&lt;p&gt;The package requires PHP 8.2 or newer and uses strict typing throughout its codebase. It also uses readonly constructor properties, a small but important design choice that makes an object’s initialized dependencies harder to mutate accidentally. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although the project offers first-class Laravel 10–13 conveniences—an auto-discovered service provider, publishable configuration, and a &lt;code&gt;Bitget&lt;/code&gt; facade—the core SDK does not have a hard &lt;code&gt;illuminate/*&lt;/code&gt; dependency outside of Laravel applications. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt; That distinction matters. A Laravel team can adopt the package with familiar configuration conventions, while a CLI worker, Symfony project, or framework-free PHP application can use the same client directly.&lt;/p&gt;

&lt;p&gt;Installation is a single Composer command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require tigusigalpa/bitget-php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a Laravel application, credentials can live in configuration backed by environment variables. For a standalone service, they can be loaded through the project’s own secure configuration mechanism. In either case, API credentials should not be committed to source control or embedded into a front-end application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1 REST coverage: the useful essentials
&lt;/h2&gt;

&lt;p&gt;The project is transparent about its scope. Rather than implying that every endpoint in the Bitget UTA v3 surface is wrapped, it documents precisely which operations are included in Phase 1 and which are not. &lt;a href="https://github.com/tigusigalpa/bitget-php/blob/main/docs/endpoints.md" rel="noopener noreferrer"&gt;2&lt;/a&gt; That is a meaningful advantage for planning: teams can decide early whether the current surface matches their workflow rather than discovering a missing endpoint late in implementation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Supported SDK methods in Phase 1&lt;/th&gt;
&lt;th&gt;Typical role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public market data&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Market::getInstruments()&lt;/code&gt;, &lt;code&gt;Market::getTickers()&lt;/code&gt;, &lt;code&gt;Market::getOrderBook()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Discover instruments, display prices, and obtain order-book data. &lt;a href="https://github.com/tigusigalpa/bitget-php/blob/main/docs/endpoints.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private account operations&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Account::getAssets()&lt;/code&gt;, &lt;code&gt;Account::getSettings()&lt;/code&gt;, &lt;code&gt;Account::setLeverage()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Read available assets and settings, then adjust leverage where supported. &lt;a href="https://github.com/tigusigalpa/bitget-php/blob/main/docs/endpoints.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private trade operations&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Trade::placeOrder()&lt;/code&gt;, &lt;code&gt;Trade::modifyOrder()&lt;/code&gt;, &lt;code&gt;Trade::cancelOrder()&lt;/code&gt;, &lt;code&gt;Trade::getOpenOrders()&lt;/code&gt;, &lt;code&gt;Trade::getOrderHistory()&lt;/code&gt;, &lt;code&gt;Trade::getPositions()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Manage an order lifecycle and reconcile positions. &lt;a href="https://github.com/tigusigalpa/bitget-php/blob/main/docs/endpoints.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is enough to support a well-defined first version of several products: a market-monitoring page, a controlled order-entry service, a bot that watches positions, or an internal trading-operations console. The SDK’s endpoint map also links every listed method to its corresponding Bitget API documentation, helping engineers trace an integration requirement from application code back to the exchange specification. &lt;a href="https://github.com/tigusigalpa/bitget-php/blob/main/docs/endpoints.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Equally important, the repository documents the current boundaries. In Phase 1, it does not include areas such as transfers, Trading Bot, Copy Trading, RFQ, Fiat, Finance/earn, batch orders, plan/trigger orders, or the other UTA v3 REST endpoints. &lt;a href="https://github.com/tigusigalpa/bitget-php/blob/main/docs/endpoints.md" rel="noopener noreferrer"&gt;2&lt;/a&gt; A reliable promotion should say this plainly: the library is a focused foundation, not an unsupported promise of complete coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safer order code starts in the demo environment
&lt;/h2&gt;

&lt;p&gt;The project provides a direct demo-trading mode. When &lt;code&gt;demoTrading: true&lt;/code&gt;—or the &lt;code&gt;BITGET_DEMO=true&lt;/code&gt; setting—is used with Demo API credentials, REST requests include Bitget’s required &lt;code&gt;paptrading: 1&lt;/code&gt; header. The SDK also exposes demo public and private WebSocket URLs. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following example follows the repository’s safety pattern. An order is both configured for demo trading and guarded by an explicit environment-variable opt-in. The intentionally distant limit price helps prevent an accidental fill during a test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tigusigalpa\Bitget\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bitget.api_key'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;secretKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bitget.secret_key'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;passphrase&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bitget.passphrase'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;demoTrading&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Do not send an order unless the explicit gate is enabled.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BITGET_ENABLE_TRADING'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'category'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'SPOT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'symbol'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'BTCUSDT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'side'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'buy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'orderType'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'limit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'10000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'qty'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'0.001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That pattern does not replace testing, permissions management, risk limits, or review. It does, however, make the safer path easier to follow. A team can wire up application behavior in a simulated environment, exercise success and failure paths, and remove the deliberate gate only after it has made a conscious production decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed errors make recovery logic explicit
&lt;/h2&gt;

&lt;p&gt;Exchange error handling should not collapse every failure into one generic exception. An invalid API key, a rate-limit response, insufficient funds, and an order lookup failure lead to very different recovery actions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bitget-php&lt;/code&gt; exposes typed exceptions including &lt;code&gt;AuthenticationException&lt;/code&gt;, &lt;code&gt;RateLimitException&lt;/code&gt;, &lt;code&gt;InvalidParameterException&lt;/code&gt;, &lt;code&gt;InsufficientFundsException&lt;/code&gt;, and &lt;code&gt;OrderNotFoundException&lt;/code&gt;. Each inherits from the SDK’s exception model and retains Bitget’s raw error code; a general &lt;code&gt;BitgetException&lt;/code&gt; also gives access to the raw response. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tigusigalpa\Bitget\Exceptions\AuthenticationException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tigusigalpa\Bitget\Exceptions\InsufficientFundsException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tigusigalpa\Bitget\Exceptions\RateLimitException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tigusigalpa\Bitget\Exceptions\BitgetException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="cm"&gt;/* order payload */&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AuthenticationException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Rotate or correct credentials; do not retry blindly.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InsufficientFundsException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Notify the workflow that the required balance or margin is unavailable.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RateLimitException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Back off before retrying according to your application policy.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BitgetException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Record $e-&amp;gt;bitgetCode and $e-&amp;gt;rawResponse for diagnostics.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value here is architectural. Application code can decide which failures are retriable, which should create an alert, and which should immediately stop a workflow. That leads to clearer observability and safer automation than a single catch-all branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time data with reconnection built in
&lt;/h2&gt;

&lt;p&gt;A WebSocket is often the right channel for reacting to market updates or private fill notifications, but it needs operational safeguards. The SDK’s &lt;code&gt;WebsocketClient&lt;/code&gt; uses a pluggable &lt;code&gt;ConnectionInterface&lt;/code&gt;. It ships with a synchronous &lt;code&gt;textalk/websocket&lt;/code&gt; adapter, while projects using ReactPHP, Amp, or Laravel Octane can implement their own adapter for a non-blocking event loop. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the documented shape of a public ticker subscription:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tigusigalpa\Bitget\WebsocketClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebsocketClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebsocketClient&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_PUBLIC_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$ws&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$ws&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'instType'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'SPOT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'topic'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'ticker'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'symbol'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'BTCUSDT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$ws&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$push&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$push&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;listen()&lt;/code&gt; is a blocking call in the default synchronous transport. It responds to Bitget’s text-frame ping/pong heartbeat and, after an unexpected disconnection, reconnects with exponential backoff from one second to a 60-second cap before resubscribing every channel that was active. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt; This turns a raw socket connection into a more usable building block for long-running workers.&lt;/p&gt;

&lt;p&gt;For private streams, credentials can be passed to the client constructor and &lt;code&gt;connect()&lt;/code&gt; performs authentication. The repository includes a &lt;code&gt;fast-fill&lt;/code&gt; example for UTA order-fill events. The subscription and listener are otherwise channel-agnostic, meaning a developer can subscribe to other Bitget channels by supplying the appropriate payload and decoding the returned array. The documentation is careful to distinguish that flexibility from endpoint-level support: beyond &lt;code&gt;fast-fill&lt;/code&gt;, additional channel payloads are not separately wrapped or decoded by Phase 1. &lt;a href="https://github.com/tigusigalpa/bitget-php/blob/main/docs/endpoints.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A focused starting point for PHP teams
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bitget-php&lt;/code&gt; is compelling because it concentrates on the reliability details that are easy to overlook: strings for API decimal fields, strict PHP conventions, injectable transport, typed exceptions, carefully handled logs, and a WebSocket client that tries to restore its prior state after a disconnect. It also does not conceal its roadmap. The public endpoint map makes current coverage visible and invites contributions to extend it. &lt;a href="https://github.com/tigusigalpa/bitget-php/blob/main/docs/endpoints.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are building a PHP or Laravel service around Bitget UTA v3, the library is worth evaluating in a demo environment first. Install it, inspect the endpoint map against your requirements, and use the documented examples to validate your authentication, error handling, and real-time design before any production rollout.&lt;/p&gt;

&lt;p&gt;Visit the project on GitHub: &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;tigusigalpa/bitget-php&lt;/a&gt;. Feedback, bug reports, and endpoint-coverage contributions are welcome through the repository. &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is for software-development information only. It does not constitute investment, trading, or financial advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>bitget</category>
      <category>trading</category>
    </item>
    <item>
      <title>Introducing bitget-go: An Idiomatic Go SDK for Bitget UTA v3</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Sun, 09 Aug 2026 06:33:12 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/introducing-bitget-go-an-idiomatic-go-sdk-for-bitget-uta-v3-4ceb</link>
      <guid>https://dev.to/tigusigalpa/introducing-bitget-go-an-idiomatic-go-sdk-for-bitget-uta-v3-4ceb</guid>
      <description>&lt;p&gt;Building a trading service in Go should mean spending time on execution logic, risk controls, and observability—not reimplementing request signatures, decoding generic JSON maps, or rebuilding WebSocket recovery loops for every project.&lt;/p&gt;

&lt;p&gt;That is the problem &lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;&lt;strong&gt;bitget-go&lt;/strong&gt;&lt;/a&gt; aims to solve. It is an open-source, idiomatic Go SDK for the &lt;strong&gt;Bitget Unified Trading Account (UTA) API v3&lt;/strong&gt;, providing typed REST access to essential market, account, and trading operations alongside a reconnecting WebSocket client.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The short version:&lt;/strong&gt; &lt;code&gt;bitget-go&lt;/code&gt; gives Go developers typed API models, context-aware network calls, exact string representations for financial values, and automatic WebSocket reconnection—while keeping the runtime dependency footprint intentionally small.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post looks at why those design decisions matter and how you can make your first request or stream market data in just a few lines of Go.&lt;/p&gt;




&lt;h2&gt;
  
  
  The friction behind a “simple” exchange integration
&lt;/h2&gt;

&lt;p&gt;A raw exchange API integration tends to accumulate infrastructure code quickly. Authentication must match the exchange’s HMAC signing rules; query parameters and request bodies must be encoded exactly; remote errors need useful application-level handling; and persistent streaming connections need recovery after the inevitable network interruption. None of those concerns is the business logic of a trading system, but all of them can affect its reliability.&lt;/p&gt;

&lt;p&gt;There is also a deceptively important data-modeling concern: &lt;strong&gt;numeric precision&lt;/strong&gt;. Exchange payloads contain prices, quantities, PnL, and fees. Converting decimal values directly into binary &lt;code&gt;float64&lt;/code&gt; can introduce representation artifacts. For a trading application, it is often safer to preserve the exact wire value and perform arithmetic with &lt;code&gt;math/big.Rat&lt;/code&gt; or a decimal package selected by the application.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bitget-go&lt;/code&gt; treats those operational details as first-class SDK responsibilities. It returns monetary and quantity fields as strings, exposes typed models rather than &lt;code&gt;interface{}&lt;/code&gt;, and takes &lt;code&gt;context.Context&lt;/code&gt; as the first argument of every network call.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Common integration concern&lt;/th&gt;
&lt;th&gt;How &lt;code&gt;bitget-go&lt;/code&gt; addresses it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request authentication&lt;/td&gt;
&lt;td&gt;Signs REST requests and sets the required headers internally.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Precision-sensitive values&lt;/td&gt;
&lt;td&gt;Keeps prices, quantities, PnL, and fees as strings instead of coercing them to &lt;code&gt;float64&lt;/code&gt;.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cancellations and deadlines&lt;/td&gt;
&lt;td&gt;Accepts &lt;code&gt;context.Context&lt;/code&gt; on every network call.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response parsing&lt;/td&gt;
&lt;td&gt;Uses typed models and a generic &lt;code&gt;models.BitgetResponse[T]&lt;/code&gt; envelope.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection interruptions&lt;/td&gt;
&lt;td&gt;Reconnects WebSockets with exponential backoff and restores subscriptions.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testability&lt;/td&gt;
&lt;td&gt;Allows a custom &lt;code&gt;*http.Client&lt;/code&gt; to be injected through functional options.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The result is not a wrapper that hides Go’s standard idioms. Instead, it makes them central to the API.&lt;/p&gt;




&lt;h2&gt;
  
  
  A deliberately Go-native client
&lt;/h2&gt;

&lt;p&gt;The SDK’s public surface is designed around conventions Go developers already expect. A REST client is constructed once, services are grouped by responsibility, and methods return typed values plus an &lt;code&gt;error&lt;/code&gt;. If a caller cancels a context or its deadline expires, the cancellation flows through the request instead of being concealed behind a custom concurrency abstraction.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The package also favors a lightweight dependency profile. According to its README, &lt;code&gt;gorilla/websocket&lt;/code&gt; is the only required runtime dependency; &lt;code&gt;stretchr/testify&lt;/code&gt; is used for tests.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; This is a sensible default for services where transparent dependency trees, quick builds, and straightforward vendoring matter.&lt;/p&gt;

&lt;p&gt;Installation requires Go 1.21 or later and follows the usual Go module workflow:&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/tigusigalpa/bitget-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Your first REST request
&lt;/h3&gt;

&lt;p&gt;The following example asks for the spot BTC/USDT ticker. Notice what is absent from the application: no manually constructed signature, no hand-written JSON response struct, and no &lt;code&gt;float64&lt;/code&gt; conversion for the reported last price.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="n"&gt;bitget&lt;/span&gt; &lt;span class="s"&gt;"github.com/tigusigalpa/bitget-go"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/tigusigalpa/bitget-go/models"&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bitget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BITGET_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BITGET_SECRET_KEY"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BITGET_PASSPHRASE"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Market&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetTickers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CategorySpot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"BTCUSDT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BTC/USDT last price: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastPrice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client signs the request, applies the relevant headers, parses the exchange response, and delivers typed ticker data to the caller.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; For a production service, replace &lt;code&gt;context.Background()&lt;/code&gt; with a context that has an intentional deadline, so an obsolete request does not outlive the trading decision that initiated it.&lt;/p&gt;




&lt;h2&gt;
  
  
  REST coverage for the first phase
&lt;/h2&gt;

&lt;p&gt;The project is candid about its current scope: &lt;strong&gt;Phase 1&lt;/strong&gt; focuses on core REST services plus WebSockets rather than claiming total endpoint coverage. That makes the package particularly useful for projects that need a strong foundation for market observation, account state, and order lifecycle management today.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Available methods in Phase 1&lt;/th&gt;
&lt;th&gt;Typical application use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Market&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GetInstruments&lt;/code&gt;, &lt;code&gt;GetTickers&lt;/code&gt;, &lt;code&gt;GetOrderBook&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Discover instruments, display prices, evaluate liquidity.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Account&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GetAssets&lt;/code&gt;, &lt;code&gt;GetSettings&lt;/code&gt;, &lt;code&gt;SetLeverage&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Read balances and configuration; update leverage where appropriate.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PlaceOrder&lt;/code&gt;, &lt;code&gt;ModifyOrder&lt;/code&gt;, &lt;code&gt;CancelOrder&lt;/code&gt;, &lt;code&gt;GetOpenOrders&lt;/code&gt;, &lt;code&gt;GetOrderHistory&lt;/code&gt;, &lt;code&gt;GetPositions&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Implement an order workflow and monitor its outcome.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The repository’s endpoint documentation is the authoritative place to check precise paths, request parameters, and the status of individual models before you depend on an endpoint in production.&lt;a href="https://github.com/tigusigalpa/bitget-go/blob/main/docs/endpoints.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  WebSockets that recover instead of merely connect
&lt;/h2&gt;

&lt;p&gt;REST is ideal for commands and snapshots; it is not the preferred path for a real-time ticker or execution feed. A reliable streaming client needs to plan for unexpected disconnections. &lt;code&gt;bitget-go&lt;/code&gt; provides both public and private WebSocket clients and exposes incoming pushes through Go channels.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a minimal public ticker subscription:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bitget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPublicWSClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;pushes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WSArg&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;InstType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"SPOT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Topic&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"ticker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Symbol&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"BTCUSDT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;pushes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For authenticated streams, create the client with &lt;code&gt;NewPrivateWSClient(apiKey, secretKey, passphrase)&lt;/code&gt;; authentication occurs during &lt;code&gt;Connect&lt;/code&gt;.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; On an unexpected disconnect, the implementation retries with an exponential backoff beginning at one second and capped at 60 seconds, then resubscribes to channels opened earlier.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; The Phase 1 private channel is &lt;code&gt;fast-fill&lt;/code&gt;; the same subscription and raw-data shape can be used for other available channels as the project expands.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That recovery behavior is especially valuable in long-running worker processes. It reduces the amount of state-reconciliation code each consumer has to write, while still letting the application decide how it should process, persist, or validate the messages it receives.&lt;/p&gt;




&lt;h2&gt;
  
  
  Errors that work with the standard library
&lt;/h2&gt;

&lt;p&gt;A good Go SDK should not force a bespoke error framework on its users. &lt;code&gt;bitget-go&lt;/code&gt; returns regular Go errors: callers can use &lt;code&gt;errors.Is&lt;/code&gt; with the package’s sentinel errors and &lt;code&gt;errors.As&lt;/code&gt; to access a typed &lt;code&gt;*BitgetError&lt;/code&gt; containing an API error code and message.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetAssets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bitget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"authentication failed — check credentials"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bitget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BitgetError&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bitget error %s: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"request failed: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This small distinction improves operational handling. Authentication failures can be surfaced to configuration management, exchange business errors can be logged with their code, and network or timeout errors can follow a retry policy appropriate to the application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start with demo trading, not real funds
&lt;/h2&gt;

&lt;p&gt;Trading code deserves a careful rollout. The SDK supports Bitget demo trading through &lt;code&gt;bitget.WithDemoTrading()&lt;/code&gt;, which sends the &lt;code&gt;paptrading: 1&lt;/code&gt; header with REST requests.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; The included REST example additionally gates a trading action behind both &lt;code&gt;BITGET_DEMO=1&lt;/code&gt; and &lt;code&gt;BITGET_ENABLE_TRADING=1&lt;/code&gt;, helping prevent accidental execution while exploring the codebase.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bitget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BITGET_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BITGET_SECRET_KEY"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BITGET_PASSPHRASE"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;bitget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithDemoTrading&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Use demo API credentials when demo mode is enabled. The project documentation warns that combining demo mode with production credentials will fail.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The client is also configurable without turning its constructor into a long, brittle parameter list. Functional options support a custom HTTP client, REST base URL, timeout, logger, locale, and demo mode.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; Injecting an &lt;code&gt;*http.Client&lt;/code&gt; is particularly useful for tracing, proxies, custom TLS transport, and &lt;code&gt;httptest&lt;/code&gt;-based unit tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bitget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BITGET_API_KEY"&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BITGET_SECRET_KEY"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BITGET_PASSPHRASE"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;bitget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHTTPClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;myProxyTransport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;bitget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithDemoTrading&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository provides offline unit tests with &lt;code&gt;go test ./...&lt;/code&gt; and optional demo-environment integration tests with &lt;code&gt;go test -tags=integration ./...&lt;/code&gt; once demo credentials are configured.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical foundation for Go trading systems
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bitget-go&lt;/code&gt; does not attempt to prescribe a strategy, a database, or a trading architecture. Its value is more focused: it handles the exchange-integration mechanics so your application can keep ownership of the decisions that truly belong to it—risk limits, position sizing, persistence, monitoring, and execution policy.&lt;/p&gt;

&lt;p&gt;The project is released under the MIT License and welcomes contributions, bug reports, and endpoint additions.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; If your stack also includes PHP or Laravel services, the author maintains a corresponding &lt;a href="https://github.com/tigusigalpa/bitget-php" rel="noopener noreferrer"&gt;&lt;code&gt;bitget-php&lt;/code&gt;&lt;/a&gt; SDK.&lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are building Bitget UTA v3 integrations in Go, clone the repository, start with the runnable REST and WebSocket examples, and validate your workflow against demo credentials before any production rollout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/tigusigalpa/bitget-go" rel="noopener noreferrer"&gt;github.com/tigusigalpa/bitget-go&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This SDK is not affiliated with Bitget. Trading involves risk; test carefully, protect API credentials, and never commit secrets to source control.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>go</category>
      <category>bitget</category>
      <category>cryptocurrency</category>
      <category>api</category>
    </item>
    <item>
      <title>Building Robust Crypto Data Pipelines in PHP: Introducing the Token Terminal SDK</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Sun, 09 Aug 2026 06:05:28 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/building-robust-crypto-data-pipelines-in-php-introducing-the-token-terminal-sdk-21hk</link>
      <guid>https://dev.to/tigusigalpa/building-robust-crypto-data-pipelines-in-php-introducing-the-token-terminal-sdk-21hk</guid>
      <description>&lt;p&gt;The cryptocurrency and decentralized finance ecosystems generate an overwhelming amount of data every single day. For developers building financial dashboards, algorithmic trading tools, or market research platforms, accessing clean, standardized, and reliable data is absolutely critical. Token Terminal has established itself as a premier provider of fundamental financial data for the crypto space, offering institutional-grade metrics across various blockchains and decentralized applications &lt;a href="https://tokenterminal.com/docs/api-reference/introduction" rel="noopener noreferrer"&gt;1&lt;/a&gt;. However, integrating complex third-party APIs into enterprise PHP applications often requires writing significant amounts of boilerplate code to handle edge cases, rate limits, and unexpected response structures.&lt;/p&gt;

&lt;p&gt;To solve this problem and streamline the developer experience, the PHP community now has access to a dedicated solution: the &lt;code&gt;tokenterminal-php&lt;/code&gt; SDK. This new open-source package provides a robust, fully-typed, and developer-friendly PHP 8.1+ client for the Token Terminal API v2 &lt;a href="https://github.com/tigusigalpa/tokenterminal-php" rel="noopener noreferrer"&gt;2&lt;/a&gt;. Designed with modern PHP standards and framework integration in mind, it abstracts away the complexities of the underlying HTTP transport, allowing developers to focus entirely on building their applications rather than wrestling with API mechanics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge of Integrating Financial APIs
&lt;/h2&gt;

&lt;p&gt;When working with comprehensive financial data APIs like Token Terminal, developers frequently encounter several architectural challenges. First, there is the issue of rate limiting. Token Terminal enforces a strict limit of 1,000 requests per minute &lt;a href="https://tokenterminal.com/docs/api-reference/best-practices" rel="noopener noreferrer"&gt;3&lt;/a&gt;. When building data pipelines that ingest historical metrics across hundreds of assets, hitting this limit is practically guaranteed. A naive implementation will simply crash or drop data, requiring manual intervention.&lt;/p&gt;

&lt;p&gt;Second, the cryptocurrency space moves rapidly. Projects frequently rebrand, merge, or migrate to new smart contracts. The Token Terminal API handles this gracefully by issuing HTTP 308 Permanent Redirects when a requested project ID has been renamed &lt;a href="https://tokenterminal.com/docs/api-reference/best-practices" rel="noopener noreferrer"&gt;3&lt;/a&gt;. However, standard HTTP clients often require explicit configuration to follow these redirects correctly while preserving the original request context and authentication headers.&lt;/p&gt;

&lt;p&gt;Finally, there is the challenge of partial success. When requesting data for multiple metrics simultaneously, some metric IDs might be valid while others are deprecated or misspelled. A rigid API client might throw an exception and discard the entire response, forcing the developer to parse raw JSON to salvage the valid data. Building a robust client that gracefully handles these scenarios requires careful architectural planning and extensive testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the Token Terminal PHP SDK
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;tokenterminal-php&lt;/code&gt; SDK was created specifically to address these integration challenges while providing a fluent, modern PHP interface. It acts as a comprehensive bridge between your PHP application and the Token Terminal infrastructure, ensuring that your data pipelines remain resilient and maintainable &lt;a href="https://github.com/tigusigalpa/tokenterminal-php" rel="noopener noreferrer"&gt;2&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comprehensive Endpoint Coverage
&lt;/h3&gt;

&lt;p&gt;One of the primary strengths of the SDK is its complete coverage of the Token Terminal API v2. It supports all 24 documented endpoints out of the box. Whether you need to fetch a list of supported market sectors, retrieve deep financial statements for a specific decentralized protocol, or access specialized datasets like the crypto screener and insider transactions, the SDK provides a dedicated, strongly-typed method for the job.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API Domain&lt;/th&gt;
&lt;th&gt;Available Data&lt;/th&gt;
&lt;th&gt;Example SDK Method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Assets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Individual token metrics and historical data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$client-&amp;gt;assets()-&amp;gt;historicalMetrics($id, $req)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Projects&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Protocol financial statements and aggregations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$client-&amp;gt;projects()-&amp;gt;financialStatement($id, $req)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Market Sectors&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Categorized industry segments&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$client-&amp;gt;marketSectors()-&amp;gt;all()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metrics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Specific data points across the ecosystem&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$client-&amp;gt;metrics()-&amp;gt;data($id, $req)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Datasets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pre-compiled research and screening data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$client-&amp;gt;datasets()-&amp;gt;cryptoScreener($req)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Built for Resilience and Reliability
&lt;/h3&gt;

&lt;p&gt;Reliability is paramount when dealing with financial data. The SDK implements sophisticated retry logic to ensure that transient network issues or rate limits do not disrupt your application flow. When the client encounters an HTTP 429 Too Many Requests response, or a 5xx server error, it automatically initiates a retry sequence using exponential backoff and jitter &lt;a href="https://github.com/tigusigalpa/tokenterminal-php" rel="noopener noreferrer"&gt;2&lt;/a&gt;. Furthermore, it natively respects the &lt;code&gt;Retry-After&lt;/code&gt; header provided by the Token Terminal API, ensuring that your application waits exactly as long as required before attempting the request again.&lt;/p&gt;

&lt;p&gt;This resilience extends to how the SDK handles the aforementioned 308 redirects. If a project undergoes a rebranding and its identifier changes, the SDK transparently follows the redirect, retrieves the data using the new identifier, and returns the result to your application without requiring any code changes on your end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graceful Handling of Partial Success
&lt;/h3&gt;

&lt;p&gt;Perhaps one of the most developer-friendly features of the &lt;code&gt;tokenterminal-php&lt;/code&gt; package is its approach to partial success responses. When querying multiple metrics, Token Terminal may return valid data alongside an array of errors for the invalid parameters. Instead of throwing a generic exception and discarding the payload, the SDK encapsulates the response in an immutable &lt;code&gt;TokenTerminalResult&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;This object allows developers to easily access both the successful data payload and the specific error details. You can iterate through the valid data to populate your database while simultaneously logging the errors for the invalid metric IDs, ensuring zero data loss during complex batch operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with the SDK
&lt;/h2&gt;

&lt;p&gt;Integrating the SDK into your project is straightforward. Because it relies on the PSR-18 standard for HTTP clients, it is highly decoupled and framework-agnostic. While Guzzle is provided as the default transport, you can easily substitute it with any PSR-18 compatible client of your choosing.&lt;/p&gt;

&lt;p&gt;Installation is handled via Composer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require tigusigalpa/tokenterminal-php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, initializing the client requires nothing more than your API key. You can instantiate it directly from your environment variables or build a custom configuration object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tigusigalpa\TokenTerminal\TokenTerminalClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize the client using your API key&lt;/span&gt;
&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TokenTerminalClient&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'your-api-key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch all supported projects and iterate through the results&lt;/span&gt;
&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' (ID: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;")&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The configuration architecture is entirely immutable. If you need to adjust timeout settings or modify the retry behavior for a specific task, you can use the fluent &lt;code&gt;with*()&lt;/code&gt; methods to generate a new configuration instance without altering the global state of your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exception Handling
&lt;/h3&gt;

&lt;p&gt;The SDK provides a granular exception hierarchy, allowing developers to catch and handle specific HTTP errors cleanly. Instead of parsing status codes manually, you can catch &lt;code&gt;UnauthorizedException&lt;/code&gt; for invalid keys, &lt;code&gt;RateLimitException&lt;/code&gt; for quota issues, or a general &lt;code&gt;ApiException&lt;/code&gt; as a fallback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tigusigalpa\TokenTerminal\Exceptions\RateLimitException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tigusigalpa\TokenTerminal\Exceptions\NotFoundException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'uniswap'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RateLimitException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The SDK handles retries automatically, but if max attempts are exceeded:&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Rate limit exceeded. Try again after: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRetryAfter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NotFoundException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Project not found in the Token Terminal registry."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  First-Class Laravel Integration
&lt;/h2&gt;

&lt;p&gt;While the SDK is perfectly suited for vanilla PHP applications, it truly shines when integrated into the Laravel ecosystem. The package includes auto-discovery, meaning the service provider and facade are registered automatically upon installation.&lt;/p&gt;

&lt;p&gt;Laravel developers can publish the configuration file to their &lt;code&gt;config&lt;/code&gt; directory and manage their API credentials directly through the standard &lt;code&gt;.env&lt;/code&gt; file. Once configured, accessing the Token Terminal API becomes as simple as calling the facade from anywhere in your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Tigusigalpa\TokenTerminal\Laravel\Facades\TokenTerminal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch the revenue breakdown for Uniswap&lt;/span&gt;
&lt;span class="nv"&gt;$metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TokenTerminal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;metricAggregations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'uniswap'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This zero-configuration approach significantly reduces the time to market for Laravel-based financial applications and analytics dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building reliable data pipelines in the cryptocurrency space requires tools that can handle the unique challenges of the ecosystem. The &lt;code&gt;tokenterminal-php&lt;/code&gt; SDK provides PHP developers with a powerful, resilient, and elegant solution for integrating Token Terminal's comprehensive financial data into their applications. By abstracting away rate limits, redirects, and complex error handling, it allows you to focus on extracting insights and delivering value to your users.&lt;/p&gt;

&lt;p&gt;If you are building data-driven applications in PHP, we highly encourage you to explore the package. You can view the source code, read the extensive documentation, and contribute to the project on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explore the repository:&lt;/strong&gt; &lt;a href="https://github.com/tigusigalpa/tokenterminal-php" rel="noopener noreferrer"&gt;tigusigalpa/tokenterminal-php on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>tokenterminal</category>
      <category>bitcoin</category>
    </item>
    <item>
      <title>Build Resilient Web3 Data Pipelines in Go with tokenterminal-go</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Wed, 05 Aug 2026 12:32:12 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/build-resilient-web3-data-pipelines-in-go-with-tokenterminal-go-1lef</link>
      <guid>https://dev.to/tigusigalpa/build-resilient-web3-data-pipelines-in-go-with-tokenterminal-go-1lef</guid>
      <description>&lt;p&gt;When a Go application needs on-chain and protocol-level data, the HTTP request itself is usually the easy part. The difficult work starts afterward: defining request models, decoding inconsistent payloads, respecting rate limits, recovering from transient failures, and deciding what to do when one part of a multi-metric query succeeds while another part does not. Those concerns can quietly turn a small integration into a maintenance burden.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;code&gt;tokenterminal-go&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; is an open-source, production-oriented Go SDK for Token Terminal API v2 that aims to remove that plumbing. The project supports all 24 documented API routes across Assets, Projects, Market Sectors, Metrics, and Datasets; it requires Go 1.21 or newer and uses only the Go standard library. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; It is a focused choice for engineers building internal analytics services, data jobs, dashboards, research tooling, or any application that needs Token Terminal data without hand-rolling an HTTP client.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The practical promise:&lt;/strong&gt; keep the integration idiomatic and type-aware, while the client handles the failure modes that normally appear only after an application reaches real traffic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why an SDK matters here
&lt;/h2&gt;

&lt;p&gt;Token Terminal’s API gives programmatic access to its data, but it requires an API key and an API-enabled plan. &lt;a href="https://tokenterminal.com/docs/api-reference/introduction" rel="noopener noreferrer"&gt;2&lt;/a&gt; That makes the client layer part of the application’s operational surface: it needs to handle credentials, request timeouts, rate limits, pagination or filtering parameters where relevant, and failures that should not crash a larger data pipeline.&lt;/p&gt;

&lt;p&gt;The library addresses these needs with a small, deliberate design. Its client methods take a &lt;code&gt;context.Context&lt;/code&gt;, its response envelopes use generic &lt;code&gt;Result[T]&lt;/code&gt; types, and its errors can be inspected with standard Go mechanisms such as &lt;code&gt;errors.Is&lt;/code&gt; and &lt;code&gt;errors.As&lt;/code&gt;. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; In other words, callers can keep control of cancellation and business policy instead of receiving opaque, string-only errors.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;What it means in practice&lt;/th&gt;
&lt;th&gt;Why it is useful&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Zero external dependencies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The package uses the standard library rather than adding third-party runtime packages. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;A smaller dependency surface makes the SDK easier to audit, vendor, and upgrade.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type-safe API models&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Typed request structures and generic &lt;code&gt;Result[T]&lt;/code&gt; envelopes are used across the client. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Editors provide better completion, and more mistakes are caught before a request is sent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context-aware calls&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every client method accepts &lt;code&gt;context.Context&lt;/code&gt;. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;A service can enforce deadlines or stop in-flight work when a request is cancelled.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retry with backoff&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GET requests can be retried for rate limiting, server errors, and transient network failures. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Temporary failures are less likely to become application-visible outages.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Partial-success preservation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Valid &lt;code&gt;data&lt;/code&gt; and API-supplied &lt;code&gt;errors&lt;/code&gt; are both retained in the result. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;A single invalid metric does not have to discard all usable data from the same response.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency-safe client&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One &lt;code&gt;Client&lt;/code&gt; may be shared safely across goroutines. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Parallel collection jobs do not need to create a separate client for each worker.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  A fast path from API key to useful data
&lt;/h2&gt;

&lt;p&gt;Installing the package follows the normal Go workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/tigusigalpa/tokenterminal-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client can be created with an API key, environment-driven configuration, or functional options. The repository documents options for the base URL, a custom HTTP client, timeout, retry count and delay, User-Agent, and opt-in POST retries. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; The following example is adapted from the project’s historical-metrics example. It requests Uniswap fees and revenue on Ethereum for a specified time range, then prints any partial issues rather than throwing away the successful data. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go/blob/main/examples/project_metrics/main.go" rel="noopener noreferrer"&gt;3&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="n"&gt;tt&lt;/span&gt; &lt;span class="s"&gt;"github.com/tigusigalpa/tokenterminal-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TOKEN_TERMINAL_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TOKEN_TERMINAL_API_KEY is not set"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"create client: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2025-01-01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2025-01-31"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HistoricalMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s"&gt;"uniswap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HistoricalMetricsParams&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;MetricIDs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"fees"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"revenue"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;ChainIDs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"ethereum"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;End&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;OrderDirection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderAscending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"load historical metrics: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"received %d data points&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errors&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"partial issue: %s %s=%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the essential advantage of the SDK: the application code describes the question—&lt;em&gt;which project, which chain, which metrics, which dates&lt;/em&gt;—rather than manually assembling URLs and decoding generic maps. The parameter structure makes filters explicit, while the &lt;code&gt;Result&lt;/code&gt; object gives callers access to both the returned data and granular API feedback. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go/blob/main/examples/project_metrics/main.go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Coverage without a maze of wrappers
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tokenterminal-go&lt;/code&gt; does not stop at a single “get metrics” helper. The repository’s endpoint coverage map groups the 24 routes into five service areas. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; That makes the SDK suitable for broader workflows that need reference data, detailed time series, aggregates, and curated datasets in the same Go codebase.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service area&lt;/th&gt;
&lt;th&gt;Representative SDK methods&lt;/th&gt;
&lt;th&gt;Typical use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Assets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;client.Assets.List&lt;/code&gt;, &lt;code&gt;Get&lt;/code&gt;, &lt;code&gt;HistoricalMetrics&lt;/code&gt;, &lt;code&gt;MetricsBreakdown&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Discover assets and examine an asset’s historical or aggregated metrics. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Projects&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;client.Projects.List&lt;/code&gt;, &lt;code&gt;Get&lt;/code&gt;, &lt;code&gt;HistoricalMetrics&lt;/code&gt;, &lt;code&gt;FinancialStatement&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Build protocol research pages, compare projects, or load financial statement data. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Market Sectors&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;client.MarketSectors.List&lt;/code&gt;, &lt;code&gt;Get&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Organize projects and assets by market sector. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metrics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;client.Metrics.List&lt;/code&gt;, &lt;code&gt;Data&lt;/code&gt;, &lt;code&gt;Aggregations&lt;/code&gt;, &lt;code&gt;Breakdown&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Query available metrics and retrieve detailed or summarized observations. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Datasets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;client.Datasets.CryptoScreener&lt;/code&gt;, &lt;code&gt;CohortAnalysis&lt;/code&gt;, &lt;code&gt;TrendingContracts&lt;/code&gt;, and others&lt;/td&gt;
&lt;td&gt;Start with curated screens and specialized analytical datasets. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This breadth matters because application needs evolve. A first version of a dashboard may list projects and draw one time series. A later version may need a screener, an aggregation view, or a financial-statement endpoint. With the same client abstraction across these areas, the transition does not require introducing a second API integration pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability is a feature, not an afterthought
&lt;/h2&gt;

&lt;p&gt;A resilient client should have predictable behavior under pressure. By default, &lt;code&gt;tokenterminal-go&lt;/code&gt; retries GET requests after HTTP 429 responses, 5xx responses, and transient network errors. It uses capped exponential backoff with jitter and honors a server-provided &lt;code&gt;Retry-After&lt;/code&gt; header. POST retries are intentionally disabled unless the application explicitly enables them with &lt;code&gt;WithRetryPOST()&lt;/code&gt;. Context cancellation stops retry waits immediately. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That policy is a strong default for data retrieval: reads are commonly safe to retry, while automatic retries of requests that may change server state deserve an explicit decision. It is also aligned with Token Terminal’s documentation, which calls out HTTP 429 as the rate-limit status that clients should handle. &lt;a href="https://tokenterminal.com/docs/api-reference/best-practices" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The error model is equally practical. Instead of forcing consumers to compare error strings, the SDK exports sentinel values such as &lt;code&gt;ErrUnauthorized&lt;/code&gt;, &lt;code&gt;ErrNotFound&lt;/code&gt;, and &lt;code&gt;ErrRateLimited&lt;/code&gt;, plus an &lt;code&gt;*APIError&lt;/code&gt; that exposes structured details including status code, message, and retry information. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; A caller can therefore implement a clear policy without coupling its business logic to the client’s internal wording.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Refresh configuration or surface a credential error.&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"check the Token Terminal API key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrRateLimited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIError&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rate limited; retry after %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetryAfter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"project, asset, or metric was not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Token Terminal request failed: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK also follows HTTP 308 redirects, which the project documents as a way to handle project or asset renames transparently. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; That is the kind of edge case developers rarely enjoy discovering after a production identifier changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve partial results instead of losing a whole response
&lt;/h2&gt;

&lt;p&gt;One of the more thoughtful details in &lt;code&gt;tokenterminal-go&lt;/code&gt; is its treatment of partial success. Some Token Terminal responses can contain valid data alongside an &lt;code&gt;errors&lt;/code&gt; array. The SDK keeps both. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; This matters when a query asks for several metrics, chains, or entities: one unsupported input should not automatically erase the observations that were returned successfully.&lt;/p&gt;

&lt;p&gt;A production workflow can turn this into a useful policy. Persist &lt;code&gt;result.Data&lt;/code&gt;, emit structured logs or metrics for &lt;code&gt;result.Errors&lt;/code&gt;, and alert only when the missing values break a required business rule. This approach is more robust than treating every non-empty error array as a total failure, and it gives downstream consumers a transparent view of data completeness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pair the SDK with Token Terminal’s API guidance
&lt;/h2&gt;

&lt;p&gt;The SDK gives Go applications a sound transport and type layer; efficient data architecture is still the caller’s responsibility. Token Terminal recommends maintaining an up-to-date cache or index for &lt;code&gt;/projects&lt;/code&gt; and &lt;code&gt;/metrics&lt;/code&gt;, refreshing it daily or weekly according to the application’s needs. &lt;a href="https://tokenterminal.com/docs/api-reference/best-practices" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Maintain an up-to-date cache of projects and metrics.” — Token Terminal API best practices &lt;a href="https://tokenterminal.com/docs/api-reference/best-practices" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That guidance fits naturally with &lt;code&gt;tokenterminal-go&lt;/code&gt;. Fetch the project and metric catalogs on a schedule that matches your product, store them in your preferred cache or database, and use those local records to validate user-selected identifiers before issuing more focused API calls. The SDK intentionally does not impose an invisible persistent cache; the repository documents an optional integration point so an application can choose its own caching strategy. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For aggregate questions, Token Terminal also advises using the Breakdown API instead of first downloading a time series and aggregating it client-side. According to the official guidance, that can reduce transferred data and improve response time. &lt;a href="https://tokenterminal.com/docs/api-reference/best-practices" rel="noopener noreferrer"&gt;4&lt;/a&gt; In the SDK, the relevant methods are exposed as &lt;code&gt;client.Metrics.Breakdown&lt;/code&gt; and &lt;code&gt;client.Assets.MetricsBreakdown&lt;/code&gt;, so the optimization is available without abandoning the same typed client model. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for normal Go engineering practices
&lt;/h2&gt;

&lt;p&gt;The project includes dedicated examples for basics, historical project metrics, metric breakdowns, datasets, error handling, and concurrent calls. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; It also documents conventional validation commands, including &lt;code&gt;go test ./... -v&lt;/code&gt;, &lt;code&gt;go test -race ./...&lt;/code&gt;, &lt;code&gt;gofmt -l .&lt;/code&gt;, and &lt;code&gt;go vet ./...&lt;/code&gt;. Its tests use &lt;code&gt;httptest.Server&lt;/code&gt; rather than calling the live Token Terminal API. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt; That is a welcome design choice for teams that want deterministic tests and no hidden network dependency in their CI pipelines.&lt;/p&gt;

&lt;p&gt;The package is published under the MIT license, so it is straightforward to evaluate and incorporate into an appropriate open-source or commercial Go project. &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Give your Go code the API client it deserves
&lt;/h2&gt;

&lt;p&gt;Reliable Web3 analytics infrastructure is not just about obtaining the right endpoint. It is about handling latency, retries, cancellation, redirects, structured errors, partial results, and concurrent workload patterns without distracting from the product you are actually building.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tokenterminal-go&lt;/code&gt; packages those operational details into a concise, zero-dependency SDK for Token Terminal API v2. If your Go service needs project metrics, asset data, sector information, or specialized datasets, it is worth exploring the repository, running the examples, and adapting the client to your own pipeline.&lt;/p&gt;

&lt;p&gt;Start here: &lt;a href="https://github.com/tigusigalpa/tokenterminal-go" rel="noopener noreferrer"&gt;github.com/tigusigalpa/tokenterminal-go&lt;/a&gt;. If the SDK saves your team implementation time, consider starring the project, opening an issue with feedback, or contributing an improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>go</category>
      <category>bitcoin</category>
      <category>tokenterminal</category>
      <category>cryptocurrency</category>
    </item>
    <item>
      <title>Stop Hand-Rolling HTTP Clients for Glassnode: Meet glassnode-go</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Fri, 31 Jul 2026 11:50:23 +0000</pubDate>
      <link>https://dev.to/tigusigalpa/stop-hand-rolling-http-clients-for-glassnode-meet-glassnode-go-50do</link>
      <guid>https://dev.to/tigusigalpa/stop-hand-rolling-http-clients-for-glassnode-meet-glassnode-go-50do</guid>
      <description>&lt;p&gt;Building with on-chain data is exciting right up until the integration work begins. A dashboard, research tool, alerting service, or backtesting pipeline may start with one metric, but it rarely stops there. Soon you are assembling request URLs by hand, decoding JSON into &lt;code&gt;map[string]interface{}&lt;/code&gt;, checking every response for rate-limit headers, and adding retry logic that you hope will not become the next production incident.&lt;/p&gt;

&lt;p&gt;That glue code is necessary, but it is not where a product becomes valuable. &lt;strong&gt;&lt;code&gt;glassnode-go&lt;/code&gt;&lt;/strong&gt; is designed to move that plumbing out of the way. It is an &lt;strong&gt;unofficial, community-built Go SDK for the Glassnode Basic API&lt;/strong&gt;, aimed at developers who want a more idiomatic and production-oriented integration layer. It is not affiliated with or endorsed by Glassnode; it is a focused tool for teams that would rather build with on-chain data than continually maintain HTTP wrappers around it. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“A dependency-light, production-oriented Go module for the Glassnode Basic API, built with the kind of care you'd want from a library you depend on every day.” — the &lt;code&gt;glassnode-go&lt;/code&gt; project README &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem is not fetching a number—it's operating the integration
&lt;/h2&gt;

&lt;p&gt;A raw API integration can fetch a price just fine. The difficult part begins when the application has to do it reliably: propagate cancellations through &lt;code&gt;context.Context&lt;/code&gt;, protect the API key, distinguish bad input from an exhausted quota, tune timeouts, and retrieve multiple metrics concurrently without turning the client layer into a fragile tangle of helpers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;glassnode-go&lt;/code&gt; approaches that problem with a deliberately Go-native design. The module targets &lt;strong&gt;Go 1.21+&lt;/strong&gt; and declares no third-party module dependencies, keeping the integration limited to the standard library rather than adding transitive packages to the project. &lt;a href="https://github.com/tigusigalpa/glassnode-go/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;1&lt;/a&gt; Its client uses functional options for configuration, supports custom HTTP transports, and is documented as safe for concurrent use across goroutines. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What a data service needs&lt;/th&gt;
&lt;th&gt;What &lt;code&gt;glassnode-go&lt;/code&gt; provides&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Familiar, discoverable access to standard metrics&lt;/td&gt;
&lt;td&gt;25 typed category services such as &lt;code&gt;Market&lt;/code&gt;, &lt;code&gt;Addresses&lt;/code&gt;, &lt;code&gt;Indicators&lt;/code&gt;, &lt;code&gt;Mining&lt;/code&gt;, and &lt;code&gt;Transactions&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Code is easier to navigate, review, and autocomplete. &lt;a href="https://github.com/tigusigalpa/glassnode-go/blob/main/docs/endpoint-coverage.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A way to reach newly introduced or unusual endpoints&lt;/td&gt;
&lt;td&gt;A generic &lt;code&gt;MetricsService&lt;/code&gt; for raw JSON, scalar time series, and object time series&lt;/td&gt;
&lt;td&gt;You do not have to wait for a typed wrapper to start experimenting. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clarity before making requests&lt;/td&gt;
&lt;td&gt;Runtime metadata methods for assets, metric paths, and supported parameters&lt;/td&gt;
&lt;td&gt;Fewer invalid requests and less guesswork around endpoint capabilities. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resilience under load&lt;/td&gt;
&lt;td&gt;Automatic handling of HTTP 429 responses and configurable retry behavior&lt;/td&gt;
&lt;td&gt;Rate-limit logic stays centralized instead of leaking into every call site. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safer operations&lt;/td&gt;
&lt;td&gt;Header-based API-key authentication by default and redaction in URLs and error messages&lt;/td&gt;
&lt;td&gt;Secrets are less likely to appear in logs and diagnostics. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Start with a useful result, not a pile of boilerplate
&lt;/h2&gt;

&lt;p&gt;Installation is a familiar Go command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/tigusigalpa/glassnode-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The quick-start experience is intentionally small. Put your Glassnode API key in &lt;code&gt;GLASSNODE_API_KEY&lt;/code&gt;, create the client from the environment, and request the metric through a typed service. The following example retrieves BTC price data at daily resolution. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;

    &lt;span class="n"&gt;glassnode&lt;/span&gt; &lt;span class="s"&gt;"github.com/tigusigalpa/glassnode-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;glassnode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClientFromEnv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Market&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;glassnode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MetricQuery&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Asset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"BTC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Resolution&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;glassnode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolution24h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BTC price at %d: $%.2f&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is not merely that the example is short. It encodes a useful boundary: application code talks in terms of &lt;strong&gt;market data and queries&lt;/strong&gt;, while the SDK takes responsibility for building the request, applying authentication, decoding the response, and returning a typed time series. That separation makes a dashboard handler, scheduled research job, or backtest easier to test and evolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed when you can; generic when you need to
&lt;/h2&gt;

&lt;p&gt;The primary API surface is made of category services that mirror Glassnode's documented endpoint families. For example, a dashboard can request OHLC data from &lt;code&gt;client.Market.PriceOHLC&lt;/code&gt;, activity data from &lt;code&gt;client.Addresses.ActiveCount&lt;/code&gt;, and an indicator such as SOPR from &lt;code&gt;client.Indicators.SOPR&lt;/code&gt;. The repository maintains an endpoint-coverage document that maps API paths to the corresponding SDK methods. &lt;a href="https://github.com/tigusigalpa/glassnode-go/blob/main/docs/endpoint-coverage.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But strongly typed wrappers should never become a release-cycle bottleneck. APIs evolve, new metrics appear, and some response shapes are inherently specialized. For those moments, &lt;code&gt;glassnode-go&lt;/code&gt; supplies the generic &lt;code&gt;MetricsService&lt;/code&gt;. It can return raw JSON through &lt;code&gt;Get&lt;/code&gt;, scalar time-series data through &lt;code&gt;GetTimePoints&lt;/code&gt;, or object time-series data through &lt;code&gt;GetObjectPoints&lt;/code&gt;. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Use a valid metric path even when you do not need a dedicated convenience method.&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetTimePoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/indicators/sopr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;glassnode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MetricQuery&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Asset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"BTC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Resolution&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;glassnode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolution24h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received %d SOPR observations&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That combination is especially useful in real products. Your stable, frequently used calls can remain concise and self-documenting; exploratory work, internal analytics, and recently introduced paths can still proceed without compromising the client architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discover capabilities before you spend requests
&lt;/h2&gt;

&lt;p&gt;Metric paths alone do not tell the whole story. A query may support different resolutions, assets, bulk access, currencies, or time formats depending on the underlying endpoint. Guessing at those parameters often results in failed calls that are hard to diagnose in a busy service.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;glassnode-go&lt;/code&gt; exposes a metadata layer for exactly this reason. The &lt;code&gt;MetadataService&lt;/code&gt; can list supported assets, discover metric paths, and inspect a metric's available parameters at runtime. The SDK documentation recommends this metadata-first workflow before making data calls. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/market/price_usd"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Metric: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Supported resolutions: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolutions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a product team, this is more than a convenience. It lets a configuration screen, query builder, or data pipeline validate its choices against the API instead of embedding assumptions that eventually become stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limits deserve first-class engineering
&lt;/h2&gt;

&lt;p&gt;A high-quality SDK should make the correct behavior the default behavior. In &lt;code&gt;glassnode-go&lt;/code&gt;, HTTP 429 responses trigger automatic retries for idempotent GET requests. The client uses the &lt;code&gt;x-rate-limit-reset&lt;/code&gt; header when the server supplies it and falls back to exponential backoff when it does not. It avoids retrying 400, 401, and 404 responses because those represent problems a retry is unlikely to fix. Retry behavior can be configured with &lt;code&gt;WithRetry&lt;/code&gt;. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This decision is quietly powerful. Every endpoint call retains the same clean shape, while the behavior that protects the service is standardized in one place. When the retry budget is exhausted, the caller still gets actionable context through the exported &lt;code&gt;APIError&lt;/code&gt; type and the &lt;code&gt;ErrRateLimited&lt;/code&gt; sentinel. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Indicators&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOPR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;glassnode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MetricQuery&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Asset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"BTC"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;glassnode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrRateLimited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;glassnode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIError&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Rate limit exhausted; reset in %ds"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RateLimitReset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same philosophy applies to the rest of the error model. The package exposes sentinel errors for conditions such as bad requests, unauthorized access, missing metric paths, rate limits, and internal server errors. That gives callers the familiar &lt;code&gt;errors.Is&lt;/code&gt; and &lt;code&gt;errors.As&lt;/code&gt; workflow rather than forcing production code to branch on error-message strings. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security should not be an afterthought
&lt;/h2&gt;

&lt;p&gt;An API key is an operational secret, not a configuration string that belongs in source code or a query URL. The library defaults to the &lt;code&gt;X-Api-Key&lt;/code&gt; header mode and supports query-string authentication only as an explicit opt-in for exceptional environments. It also documents API-key redaction in URLs, response metadata, and error messages. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The recommended developer experience is therefore straightforward: keep the key in &lt;code&gt;GLASSNODE_API_KEY&lt;/code&gt;, construct the client with &lt;code&gt;NewClientFromEnv()&lt;/code&gt;, and let the client carry the secret in headers. It is a small convention with a meaningful payoff in log hygiene and incident response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for dashboards today—and historical research tomorrow
&lt;/h2&gt;

&lt;p&gt;The SDK is not limited to a single-metric dashboard. Its &lt;code&gt;BulkQuery&lt;/code&gt; support can request data for explicitly specified multiple assets in one call, reducing round trips where the underlying metric supports bulk access. The project documentation also notes that the cost model remains per asset, so bulk requests optimize throughput rather than magically reducing credit consumption. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For researchers, the Point-in-Time API is another standout capability. PIT response models preserve a &lt;code&gt;computed_at&lt;/code&gt; timestamp alongside the observation timestamp. This matters when historical metric values may later be revised: a backtest can distinguish what the current dataset says from what was known at a particular historical moment. The SDK offers typed PIT response models and generic point-in-time methods for that workflow. &lt;a href="https://github.com/tigusigalpa/glassnode-go/blob/main/docs/endpoint-coverage.md" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;A practical &lt;code&gt;glassnode-go&lt;/code&gt; path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Market dashboard&lt;/td&gt;
&lt;td&gt;Typed &lt;code&gt;Market&lt;/code&gt;, &lt;code&gt;Indicators&lt;/code&gt;, and &lt;code&gt;Addresses&lt;/code&gt; services for the metrics displayed most often. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-asset research job&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Metrics.GetBulk&lt;/code&gt; with explicit asset lists, plus metadata checks before the request. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API explorer or no-code query builder&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Metadata.Assets&lt;/code&gt;, &lt;code&gt;Metadata.Metrics&lt;/code&gt;, and &lt;code&gt;Metadata.Metric&lt;/code&gt; to populate valid options dynamically. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strategy backtesting&lt;/td&gt;
&lt;td&gt;Point-in-Time response models that retain &lt;code&gt;computed_at&lt;/code&gt; for historically aware analysis. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom or newly added endpoint&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Metrics.Get&lt;/code&gt;, &lt;code&gt;GetTimePoints&lt;/code&gt;, or &lt;code&gt;GetObjectPoints&lt;/code&gt; with a valid metric path. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The developer experience is the feature
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;glassnode-go&lt;/code&gt; does not try to hide Glassnode behind a proprietary abstraction. Instead, it gives Go developers a reliable, conventional way to work with the Basic API: typed services for everyday endpoints, generic access when flexibility is needed, metadata for discovery, and production-minded defaults for context propagation, concurrency, security, and retries. The project's initial &lt;code&gt;v1.0.0&lt;/code&gt; release documents these capabilities along with typed response models, configurable transport options, and a mocked-transport test suite. &lt;a href="https://github.com/tigusigalpa/glassnode-go/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your next Go project needs on-chain metrics, take the SDK for a spin, browse the examples, and consider contributing an issue, documentation improvement, or pull request. The repository is MIT licensed and welcomes contributions. &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/tigusigalpa/glassnode-go" rel="noopener noreferrer"&gt;github.com/tigusigalpa/glassnode-go&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This post discusses developer tooling for accessing data. It is not investment, trading, or financial advice.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

</description>
      <category>go</category>
      <category>glassnode</category>
      <category>cryptocurrency</category>
      <category>bitcoin</category>
    </item>
  </channel>
</rss>
