Api Connector Builder logo

Api Connector Builder

Community
ericrisco
api-connector-builder

Use when writing a client for someone else's REST or GraphQL API: auth flow choice and token refresh, pagination to exhaustion, retry-with-jitter on transient failures only, rate-limit-aware throttling. NOT inbound callbacks (that is `webhooks`), NOT chaining services (that is `automation-flows`), NOT designing your own API (that is `api-design`).

Overview

Publisherericrisco
Repositoryrsc-harness
Skill nameapi-connector-builder
Stars
239
Forks
24
Bundled files
4
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 4 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by ericrisco on GitHub. Read the source before you install it.

Installation

Install the Api Connector Builder AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/ericrisco/rsc-harness/tree/main/skills/api-connector-builder
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/ericrisco/rsc-harness.git /tmp/rsc-harness
mkdir -p .claude/skills
cp -r /tmp/rsc-harness/skills/api-connector-builder .claude/skills/api-connector-builder
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Api Connector Builder in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Api Connector Builder on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Api Connector Builder is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

API connector builder — auth, pagination, retries, rate limits

You are writing a client for someone else's HTTP API. You do not own the contract; you obey it. The deliverable is one typed connector module per vendor that authenticates, walks the whole result set, retries only transient failures with backoff, and stays under the rate limit without getting the key banned. One connector = one vendor: mixed clients tangle two auth schemes and two rate-limit budgets into something no one can reason about.

Four pillars, every time: auth, pagination, retries, rate limits. If your connector skips any one of them it works in the demo and breaks in production — on page 2, on token expiry, on the first 429, or on a flaky network.

Step 0 — read the contract

Read the vendor docs before writing a line — invented endpoints and guessed field names 404 in prod. Extract these first; each one changes what you write, so missing one means a rewrite.

Find in their docsWhy it changes your code
Auth scheme + token TTLPicks the flow below; TTL decides if you need refresh
Base URL and versionWrong version = silent 404s or deprecated field shapes
Rate-limit header namesYou cannot throttle to a budget you cannot read
Pagination styleCursor vs offset vs Link vs Relay = different loop
Error envelope shapeWhere the real error code lives (body, not always status)
Idempotency supportDecides whether POST is safe to retry (key header?)

If a fact is not in the docs, probe one real call and read the response headers and body — do not assume.

Auth — pick the flow, then store the secret right

OAuth 2.1 is the current baseline. Three deltas you must honor: PKCE is mandatory for every authorization-code client, the Implicit and Resource-Owner-Password grants are removed, and bearer tokens may not travel in query strings (header only) — query strings end up in logs and referrers. (oauth.net/2.1, accessed 2026-06-02.)

FlowPick it whenSecret livesRefresh strategy
API key in headerSimple server-to-server, vendor issues keyenv / secret storenone; rotate manually
Bearer static tokenPersonal access token, long-livedenv / secret storenone; treat as a key
OAuth2 Client CredentialsMachine-to-machine, no end userclient_id + secretre-mint on 401; cache until expiry
OAuth2 Auth-Code + PKCEActing on behalf of a userrefresh tokenrotate on every refresh (see below)
Device flowCLI / TV / input-constrained devicerefresh tokenpoll then rotate as auth-code

Refresh-token rule (public clients): refresh tokens must be sender- constrained or one-time-use — rotated on every refresh, with the old one invalidated. Keep access tokens short-lived. Never log a raw token; log a partial or hash if you must correlate. (OAuth 2.0 Security BCP / RFC 9700, accessed 2026-06-02.) Full per-flow walkthroughs, token storage, and a DPoP note are in references/auth-flows.md.

Retries + backoff

Retry only transient failures, and only when the operation is safe to repeat.

Status / errorRetry?Note
429 Too Many Requestsyeshonor Retry-After (see rate limits)
502 / 503 / 504yesserver-side transient
Connection reset / timeout / DNSyesnetwork transient
400 / 401 / 403 / 404 / 409 / 422noreplay returns the same error — fix the call
2xxn/asuccess

Idempotency gate. GET/PUT/DELETE are idempotent by semantics and safe to retry. POST is not — only retry it if you send a stable idempotency key so the server dedupes the duplicate. (AWS Builders' Library, accessed 2026-06-02.)

Backoff with jitter. Use delay = min(cap, base * 2^attempt) + random_jitter. The jitter is not optional: without it, every client that failed at the same instant retries at the same instant — a synchronized thundering herd that DDoSes the recovering server. Full or decorrelated jitter is preferred. Always set stop conditions: max attempts 3–5, a total deadline, and a per-attempt timeout. (AWS Builders' Library, accessed 2026-06-02.)

python
# Bad: retries everything, flat sleep, no jitter, no cap, no deadline.
for _ in range(10):
    r = httpx.get(url)
    if r.status_code == 200:
        return r.json()
    time.sleep(1)  # 4xx will never recover; herds synchronize on flat 1s
python
# Good: transient-only, exponential backoff WITH jitter, bounded.
import random, time, httpx

RETRYABLE = {429, 502, 503, 504}

def get(url, *, attempts=5, base=0.5, cap=20.0, deadline=60.0):
    started = time.monotonic()
    for attempt in range(attempts):
        try:
            r = httpx.get(url, timeout=10.0)  # per-attempt timeout
        except (httpx.ConnectError, httpx.ReadTimeout):
            pass  # network transient -> fall through to backoff
        else:
            if r.status_code == 200:
                return r.json()
            if r.status_code not in RETRYABLE:
                r.raise_for_status()  # 4xx: do not retry, surface it
        if time.monotonic() - started > deadline:
            raise TimeoutError("retry deadline exceeded")
        delay = min(cap, base * 2 ** attempt) + random.uniform(0, base)
        time.sleep(delay)
    raise RuntimeError("max attempts exhausted")

In Python prefer tenacity 9.1.4 (@retry with wait_exponential_jitter, stop_after_attempt, retry_if_exception_type) over a hand loop; in Node/TS use undici (the engine behind global fetch() since Node 18) with its retry interceptor. (PyPI/tenacity, nodejs/undici, accessed 2026-06-02.)

Rate limits

Do not guess the wait — the server tells you. Precedence:

  1. Retry-After present (on a 429 or 503) → wait exactly that. It is either a number of seconds or an HTTP-date; handle both.
  2. No Retry-After → compute the wait from X-RateLimit-Reset (a Unix epoch or a delta-seconds, per vendor docs).
  3. Proactively throttle on X-RateLimit-Remaining — slow down before you hit zero rather than absorbing a wall of 429s. (iotools.cloud rate-limiting guidance, accessed 2026-06-02.)
python
# Bad: ignore the headers, hammer, eat 429s, get the key throttled or banned.
while more:
    resp = client.get(next_url)   # no remaining check, no Retry-After
    process(resp)
python
# Good: header-aware. Honor Retry-After, else reset; brake near the limit.
def wait_for_rate_limit(resp):
    ra = resp.headers.get("Retry-After")
    if ra is not None:
        return float(ra) if ra.isdigit() else _seconds_until_httpdate(ra)
    remaining = int(resp.headers.get("X-RateLimit-Remaining", "1"))
    if remaining <= 1:
        reset = float(resp.headers.get("X-RateLimit-Reset", "0"))
        return max(0.0, reset - time.time())  # or reset directly if delta-seconds
    return 0.0

For sustained pulls, gate every request through a token-bucket sized to the documented budget (e.g. 60 req/min → refill 1 token/sec, capacity 60) so bursts smooth out instead of slamming the wall.

Pagination — loop to exhaustion

There is no single best strategy; the vendor chose one and you follow it. The universal rule: loop until the API says there is no next page — never a fixed page count. A hardcoded for page in range(10) silently drops everything after page 10.

StyleSignal of "next"Trade-off
Offset / page number?offset= / ?page= until empty pagesimple; drifts on inserts, slow at depth
Cursor / keysetopaque next_cursor in bodystable under writes, fixed cost — prefer it
Link header (REST)Link: <...>; rel="next"parse the header, follow until no next
Relay (GraphQL)pageInfo.hasNextPage + endCursorpass endCursor as after next query

(graphql.org/learn/pagination + pagination pattern guides, accessed 2026-06-02.)

Expose results as a generator / async-iterator so callers stream instead of buffering everything:

python
def iter_records(client):
    cursor = None
    while True:
        page = client.get("/items", params={"cursor": cursor, "limit": 100})
        body = page.json()
        yield from body["data"]
        cursor = body.get("next_cursor")
        if not cursor:           # exhaustion signal, not a counter
            return

Full code for every style — offset, keyset, Link-header parsing, GraphQL Relay connection walking, in Python and TS, plus dedup-on-overlap notes — is in references/pagination.md.

Putting it together

A minimal connector wires all four pillars plus env config and structured logging.

python
# connector.py — Python: httpx + tenacity
import os, logging, httpx
from tenacity import retry, stop_after_attempt, wait_exponential_jitter, retry_if_exception_type

log = logging.getLogger("connector")
TOKEN = os.environ["VENDOR_API_TOKEN"]          # from env, never hardcoded

class Transient(Exception): ...

@retry(stop=stop_after_attempt(5),
       wait=wait_exponential_jitter(initial=0.5, max=20),
       retry=retry_if_exception_type(Transient))
def _request(client, method, path, **kw):
    r = client.request(method, path, timeout=10.0, **kw)   # per-request timeout
    log.info("req id=%s %s status=%s", r.headers.get("x-request-id"), path, r.status_code)
    if r.status_code in (429, 502, 503, 504):
        raise Transient(r.status_code)
    r.raise_for_status()
    return r

def client():
    return httpx.Client(base_url="https://api.vendor.com/v2",
                        headers={"Authorization": f"Bearer {TOKEN}"})  # header, not query
typescript
// connector.ts — Node/TS: global fetch (undici) + bounded retry
const TOKEN = process.env.VENDOR_API_TOKEN!;          // from env, never hardcoded
const RETRYABLE = new Set([429, 502, 503, 504]);

export async function request(path: string, init: RequestInit = {}, attempt = 0): Promise<Response> {
  const res = await fetch(`https://api.vendor.com/v2${path}`, {
    ...init,
    headers: { Authorization: `Bearer ${TOKEN}`, ...init.headers },
    signal: AbortSignal.timeout(10_000),              // per-request timeout
  });
  console.info(JSON.stringify({ id: res.headers.get("x-request-id"), path, status: res.status, attempt }));
  if (RETRYABLE.has(res.status) && attempt < 4) {
    const ra = Number(res.headers.get("retry-after"));
    const wait = Number.isFinite(ra) && ra > 0 ? ra * 1000 : Math.min(20_000, 500 * 2 ** attempt) + Math.random() * 500;
    await new Promise((r) => setTimeout(r, wait));
    return request(path, init, attempt + 1);
  }
  return res;                                          // caller checks res.ok / paginates
}

Anti-patterns

Anti-patternConsequenceFix
Retry 4xx (401/403/404/422)Burns attempts; same error every timeRetry only 429 + 5xx + network errors
for page in range(N) fixed loopSilently drops records past page NLoop on cursor / Link / hasNextPage
Flat sleep(1) between retriesSynchronized herd hammers recovering APIExponential backoff with jitter + cap
Log the token / Authorization headerLeaked credential in shipped logsLog request id + status + attempt only
Hardcode the API key in sourceLeaks via git; cannot rotate cleanlyRead from env / secret store
No request timeoutOne hung socket stalls the whole runSet a per-request timeout always
Ignore Retry-AfterKeep 429-ing; key gets throttled/bannedHonor Retry-After, else X-RateLimit-Reset
Re-POST on retry with no idempotencyDuplicate charges / recordsSend an idempotency key, or do not retry POST
Token in query stringToken ends up in logs / referrersBearer in the Authorization header
Implicit / password OAuth grantRemoved in OAuth 2.1; insecureAuth-Code + PKCE, or Client Credentials

Verify

Run scripts/verify.sh over the connector you write: it greps for hardcoded secrets, asserts a retry mechanism and a pagination loop and a request timeout exist, and flags localStorage token storage or plaintext token logging. It is a structure linter (read-only), not a behavior test — exit 0 on a clean target.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Api Connector Builder AI skill do?

Use when writing a client for someone else's REST or GraphQL API: auth flow choice and token refresh, pagination to exhaustion, retry-with-jitter on transient failures only, rate-limit-aware throttling. NOT inbound callbacks (that is `webhooks`), NOT chaining services (that is `automation-flows`), NOT designing your own API (that is `api-design`).

Why use Api Connector Builder on TypingMind?

Because you install it once and use it with any model. Api Connector Builder is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Api Connector Builder in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/ericrisco/rsc-harness/tree/main/skills/api-connector-builder. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Api Connector Builder?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Api Connector Builder?

As many as you like. As long as a model supports skills, you can use Api Connector Builder with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Api Connector Builder AI skill free?

Yes. It is published on GitHub by ericrisco under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇