• HarborClient Adds Git Support

    If your team already treats API definitions as source code—reviewed in pull requests, branched for experiments, deployed from main—your HTTP client should fit that workflow too. HarborClient now supports git-backed collections: store requests, environments, and collection metadata as files in a repository, edit them in the app, and commit, pull, and push without leaving HarborClient.

    This isn’t export/import as a side channel. Git is a first-class database provider, alongside SQLite, MySQL, PostgreSQL, and Firestore. Link a local clone, and HarborClient writes your collections to disk as structured JSON while you work.

    Why git-backed collections?

    HarborClient has long supported sharing collections through export files, database invites, and team hubs. Each approach fits a different need:

    • Export/import — great for one-off snapshots
    • Shared databases — live collections on a remote backend
    • Team hubs — token-based sharing through HarborClient Team Hub

    Git is for teams that already collaborate through repositories: readable diffs, branches, code review, and history you can blame. Put collections next to your app code or infrastructure configs and use the same rituals you use for everything else.

    How it works

    When you add a Git database connection, HarborClient stores data under a configurable subdirectory in your repo (default: .harborclient/). Each collection becomes a folder. Each saved request becomes its own file—so a change to one endpoint doesn’t rewrite a giant monolithic JSON blob.

    .harborclient/

    .gitignore

    collections/

    <uuid>-<slug>/

    collection.json # name, variables, headers, auth, scripts, folders

    requests/

    <uuid>-<slug>.json # one request per file

    environments/

    <uuid>-<slug>.json

    That layout keeps diffs small and merge conflicts localized. If two people edit different requests, git can usually merge cleanly. If they edit the same request, you get a conflict in one file—not across an entire collection.

    Variables marked Share are written to disk with their values (for team-visible config). Variables you keep private are masked on disk, same as when you export a collection manually—so secrets don’t accidentally land in the repo.

    For environments, HarborClient generates a .gitignore that ignores local override files (environments/local*.json and environments/*-local.json). Commit shared environment definitions; keep machine-specific secrets in ignored local files.

    Source control inside the app

    You don’t have to drop to a terminal for every sync.

    • Uncommitted changes badge — Git-backed collections show an amber badge in the sidebar when the working tree has changes under the HarborClient subdirectory.
    • Source control panel — From a collection’s row menu, open Source control to see your branch, change count, recent commits, and actions to Commit, Pull, and Push.
    • Live reload — After a pull—or when files change on disk from an external git pull—HarborClient reloads collections and refreshes the sidebar. A file watcher and focus-based refresh help keep the UI aligned with the repo.

    If merge conflict markers appear in JSON files, HarborClient surfaces the conflict count in the source control panel and warns you with a toast. Resolve the markers in your editor, then pull or reload again.

    Getting started

    1. Clone your repository locally (use an HTTPS URL).
    2. Open File → Settings → Databases and click Add database.
    3. Choose type Git and fill in:
      • Repository path — path to your local clone
      • Repository URL (HTTPS) — remote used for fetch and push
      • Branch — e.g. main
      • HarborClient subdirectory — default .harborclient
    4. Authenticate if the repo is private (see below).
    5. Restart HarborClient so the connection mounts at launch.

    On first use, HarborClient creates the directory layout and default .gitignore.

    Authentication for private repos

    HarborClient talks to remotes over HTTPS using isomorphic-git. Tokens are stored encrypted on your machine—the same secret storage used for AI API keys—not in plaintext connection settings.

    You have two options:

    MethodBest for
    Personal access token (PAT)GitHub, GitLab, Bitbucket, self-hosted git—any HTTPS host
    Authorize with GitHubGitHub.com via OAuth device flow: approve in the browser, complete in Settings

    Note: SSH remotes and SSH keys are not supported in-app. If your team uses git@github.com:... URLs day to day, create a PAT for HTTPS access, or use your normal git tooling for push/pull while still editing collections in HarborClient.

    Sharing without invite tokens

    Git-backed collections don’t use HarborClient Invite tokens. Sharing is through the repository itself: grant access to the repo, and teammates add the same Git connection pointing at their own clone. Everyone works from the same files, with git as the coordination layer.

    Collections on a git connection still show a provider badge in the sidebar (like other non-active databases), so you can tell which repo backs which collection.

    A practical workflow

    A typical team flow might look like this:

    1. Add a Git database connection to your API definitions repo.
    2. Create or import collections; HarborClient writes files as you edit.
    3. Commit from the Source control panel with a clear message.
    4. Push; teammates pull (in HarborClient or their terminal).
    5. Review API changes in GitHub/GitLab like any other code change.

    Branches work too: point your connection at a branch, experiment in HarborClient, merge through your normal git process.

    Try it

    Git-backed collections are available in HarborClient today. If you’ve been exporting .json files into repos by hand, or maintaining duplicate copies of the same API set, this is the workflow you were approximating—now built into the app.

    File → Settings → Databases → Add database → Git

    Clone, connect, commit. Your API collections belong in git alongside everything else you ship.

  • HarborClient: Simple, Shareable, and Built for Real Developers

    API clients should not be complicated.

    That sounds obvious, but somehow the category has drifted away from that basic truth. A tool that exists to help developers send requests, inspect responses, and organize API examples has slowly become something heavier: a platform, a workspace, a billing model, a collaboration system, and sometimes a product that feels more interested in managing developers than helping them.

    HarborClient is being built from a different starting point.

    It is simple. It is shareable. And it is built for real developers doing real work.

    Simple Does Not Mean Primitive

    There is a bad habit in software of treating simplicity as if it means weakness.

    It does not.

    Simple software can still be powerful. In fact, the best developer tools usually are. They expose the core workflow clearly, avoid unnecessary friction, and let the user move quickly without thinking about the tool more than the task.

    That is what an API client should do.

    You should be able to create a request, choose a method, enter a URL, add headers, configure authentication, send the request, and inspect the response without fighting the interface. You should be able to organize requests into collections without needing to understand a company’s entire product philosophy. You should be able to switch environments without digging through clutter.

    HarborClient is designed around that basic idea: make the common path obvious.

    The goal is not to remove capability. The goal is to remove drag.

    Built Around the Way Developers Already Work

    Developers already have workflows.

    They have projects. They have repositories. They have branches. They have pull requests. They have local environments, staging environments, production environments, and messy half-broken development services running on random ports.

    A good API client should fit into that world.

    It should not force the project to orbit around the tool. It should not require a cloud account for ordinary work. It should not make basic sharing feel like an enterprise feature. And it should not trap project knowledge inside a remote workspace when that knowledge belongs with the code.

    HarborClient is built with a developer-first assumption: your API collections are part of your project.

    They should be portable. They should be understandable. They should be easy to share. They should be able to live close to the code instead of being locked away in a proprietary cloud workspace.

    That matters because API requests are not just temporary debugging artifacts. Over time, they become living documentation. They show how the system works. They help new developers understand the API. They help frontend and backend developers stay aligned. They help QA validate behavior. They help future-you remember what current-you was thinking.

    That knowledge should not be hard to move.

    Shareable Without the Nonsense

    Sharing collections should be boring.

    That is a compliment.

    A collection is just a useful bundle of API requests. If you are working with another developer, sharing that bundle should be easy. If you are working on a team, it should be natural. If you are maintaining an open-source project, it should be possible without asking contributors to join your paid workspace.

    Too many tools treat collaboration as the moment the meter starts running.

    There is a difference between advanced enterprise collaboration and basic sharing. Enterprise features like centralized administration, audit logs, team permissions, compliance workflows, hosted sync, and organizational controls are real features. Those can justify a paid plan.

    But sharing API examples with another developer is not enterprise governance. It is normal software development.

    HarborClient is meant to respect that distinction.

    The goal is simple: let developers share useful API collections without turning a basic workflow into a subscription problem.

    Local-First by Default

    Local-first software has a practical kind of honesty.

    Your files are yours. Your project is yours. Your data does not disappear into a vendor’s workspace unless you deliberately put it there.

    That model makes sense for API tooling because API clients often contain sensitive information: internal URLs, request examples, headers, authentication flows, environment variables, and sometimes tokens or secrets that should be handled carefully.

    A local-first API client gives developers more control. It makes the tool feel predictable. It also makes collaboration through Git more natural. Collections can be versioned, reviewed, diffed, updated with the API, and included as part of the project’s working knowledge.

    That is a cleaner model than scattering API knowledge across private cloud accounts, personal scratchpads, and half-maintained shared workspaces.

    Cloud features can be useful. But they should be optional.

    The local project should remain the center of gravity.

    Built for Real Developers

    “Real developers” does not mean some elite category of programmer.

    It means people doing actual development work, with all the normal messiness that comes with it.

    Real developers test unfinished APIs. They hit endpoints before the docs are ready. They switch between local and staging. They copy tokens from one place to another. They debug weird response bodies. They need to check whether the bug is in the frontend, the backend, the proxy, the auth layer, or the third-party service.

    They do not need their API client to lecture them about workflows.

    They need it to be fast, clear, and dependable.

    HarborClient is for that kind of work. The everyday work. The practical work. The “I just need to see what this endpoint is doing” work.

    That is the work API clients should serve first.

    The Point Is Control

    At its core, HarborClient is about giving control back to the developer.

    Control over collections.

    Control over sharing.

    Control over where project knowledge lives.

    Control over whether cloud features are involved at all.

    Control over the shape of the workflow.

    Developer tools are best when they respect the developer’s judgment. They should provide useful structure without taking ownership of the process. They should help teams move faster without quietly creating new dependencies. They should make the simple things simple and the powerful things possible.

    That is the balance HarborClient is aiming for.

    A Cleaner API Client for a Messier World

    APIs are not getting simpler. Systems are more distributed. Auth flows are more layered. Teams are more remote. Local development is more complex. Testing an endpoint often means understanding half a dozen moving parts.

    That makes the API client more important, not less.

    But the answer is not more clutter. It is not more lock-in. It is not turning every saved request into a managed cloud object.

    The answer is a tool that gives developers a clean place to work.

    HarborClient is simple because the work is already complicated enough.

    It is shareable because API knowledge belongs to the project, not the platform.

    And it is built for real developers because real developers deserve tools that respect their time, their workflow, and their control.

    That is HarborClient.

  • Writing API Clients that Developers Love to Use

    A good API client does not just send HTTP requests.

    That is the bare minimum.

    A good API client makes an API feel natural inside the language where it is being used. It hides the annoying parts without hiding the important parts. It turns documentation into discoverable code. It helps developers move quickly, make fewer mistakes, and recover cleanly when something goes wrong.

    A bad API client does the opposite.

    It leaks implementation details. It makes developers memorize endpoint paths. It returns vague errors. It has inconsistent naming. It forces every project to rewrite the same pagination, authentication, retry, and response-handling code.

    Developers notice.

    And once they stop trusting your client library, they will either wrap it, avoid it, or call the API directly.

    That is not where you want to be.

    An API Client Is a Developer Experience Product

    It is tempting to think of a client library as a thin technical layer around an API.

    That mindset produces mediocre libraries.

    An API client is not just a transport wrapper. It is part of the product experience. Its users are developers, and developers are still users. They get confused. They get impatient. They skim docs. They copy examples. They want sensible defaults. They want error messages that help. They want the library to feel obvious.

    The best API clients respect that.

    They do not make the developer think about HTTP unless HTTP actually matters. They do not require the developer to remember whether the endpoint is /users/{id}/subscriptions, /subscriptions/user/{id}, or /v2/accounts/{id}/billing/subscription.

    They expose the concept directly:

    const subscription = await client.users.getSubscription(userId);

    That is the job of a client library: turn remote API behavior into code that feels local, predictable, and hard to misuse.

    Start With the Shape Developers Want

    The worst way to design an API client is to blindly mirror the HTTP API.

    Sometimes that is fine. Often it is lazy.

    A REST API is designed around resources, URLs, status codes, and network behavior. A client library is designed around code. Those are related, but they are not the same thing.

    A developer using your library should not have to think in endpoint names first. They should think in product concepts.

    Instead of this:

    await client.request("POST", "/v1/customers/123/payment_methods", {
    body: paymentMethod
    });

    Prefer this:

    await client.customers.addPaymentMethod("123", paymentMethod);

    The second version communicates intent. It is easier to autocomplete. It is easier to read in application code. It gives the library room to handle details like serialization, retries, idempotency keys, and error mapping.

    The API may be HTTP underneath.

    The client should feel like a proper library.

    Make the First Five Minutes Excellent

    Developers judge libraries fast.

    Before they read your full documentation, they want to know a few things:

    Can I install it easily?

    Can I authenticate quickly?

    Can I make one successful request?

    Can I understand the response?

    Can I handle an error?

    Your client should make that first path painfully obvious.

    A great quickstart matters more than a massive reference document. Give developers a working example that looks like real code, not a toy snippet with half the important details missing.

    For example:

    import { HarborClient } from "@harbor/client";
    const client = new HarborClient({
    apiKey: process.env.HARBOR_API_KEY
    });
    const collection = await client.collections.create({
    name: "Production API",
    description: "Requests used by the production team"
    });
    console.log(collection.id);

    That is the moment where the developer decides whether the library feels clean or annoying.

    Do not waste that moment.

    Use the Language Naturally

    A good JavaScript library should feel like JavaScript. A good Python library should feel like Python. A good Go library should feel like Go.

    This sounds obvious, but many generated clients fail here. They expose awkward names, strange parameter objects, inconsistent casing, or patterns that technically work but feel foreign in the language.

    In TypeScript, developers expect strong types, helpful autocomplete, clean async behavior, and predictable object shapes.

    In Python, developers expect readable method names, keyword arguments, and exceptions that feel natural.

    In Go, developers expect context support, explicit errors, useful structs, and minimal magic.

    Do not force every language into the same shape just because your OpenAPI generator can do it.

    Generated clients can be a useful starting point, but the final library should feel hand-finished. The extra effort shows.

    Types Are Documentation

    If your client is written for a typed ecosystem, the types are not just compiler decoration. They are part of the user interface.

    Good types teach the developer how to use the library.

    They show required fields, optional fields, return shapes, allowed string values, pagination metadata, and error structures. They reduce trips to the docs. They make autocomplete useful.

    Bad types are worse than no types.

    Avoid vague escape hatches like this unless there is truly no alternative:

    createUser(data: any): Promise<any>

    That tells the developer nothing.

    Prefer this:

    createUser(data: CreateUserInput): Promise<User>

    And make those types meaningful:

    type CreateUserInput = {
    email: string;
    name?: string;
    role?: "admin" | "member" | "viewer";
    };

    Now the editor becomes part of the documentation.

    That is a huge win.

    Design Errors Carefully

    Error handling is where many API clients fall apart.

    Some return raw HTTP responses. Some throw generic errors. Some bury useful information three levels deep. Some behave differently depending on which endpoint failed.

    Developers should not have to reverse-engineer your error system.

    A good client library should provide consistent, structured errors:

    try {
    await client.users.create({
    email: "not-an-email"
    });
    } catch (error) {
    if (error instanceof HarborApiError) {
    console.log(error.statusCode);
    console.log(error.code);
    console.log(error.message);
    console.log(error.requestId);
    }
    }

    The error should include enough information to debug the problem:

    • HTTP status code
    • API error code
    • Human-readable message
    • Request ID
    • Field-level validation details, when available
    • Whether the error is retryable, when possible

    Do not just throw Error: Request failed.

    That is useless.

    When something breaks in production, the developer using your library needs enough information to understand what happened and what to do next.

    Do Not Hide the Escape Hatch

    A client library should make common things easy, but it should not make uncommon things impossible.

    There will always be edge cases. New API endpoints may exist before the library is updated. Advanced users may need custom headers, raw responses, request hooks, or lower-level control.

    Give them a clean escape hatch.

    For example:

    await client.request({
    method: "POST",
    path: "/v1/experimental/widgets",
    body: {
    name: "Test Widget"
    }
    });

    This does not mean every developer should use the raw request method. Most should not. But its existence prevents the library from becoming a cage.

    Good abstraction is helpful.

    Forced abstraction is irritating.

    Handle Pagination Like You Care

    Pagination is one of those things every API has and every developer gets tired of rewriting.

    A quality client should make pagination pleasant.

    Do not force developers to manually loop through cursors for common use cases unless they need that control.

    Give them options.

    A simple list method:

    const users = await client.users.list({
    limit: 50
    });

    A manual pagination flow:

    const page = await client.users.list({
    cursor: nextCursor,
    limit: 50
    });

    And, when the language supports it, an iterator:

    for await (const user of client.users.listAll()) {
    console.log(user.email);
    }

    That last version is the one developers will love when they need to process everything.

    Pagination is not just an API feature. It is a usability problem. Solve it once inside the client so every application does not have to solve it again.

    Sensible Defaults Beat Endless Configuration

    Configuration is necessary.

    Too much configuration is a smell.

    A developer should not need to provide fifteen options just to make the first request. Start with sensible defaults:

    • Default API base URL
    • Reasonable timeout
    • JSON request and response handling
    • Standard retry behavior for safe retryable failures
    • Clear user agent
    • Environment-friendly authentication
    • Good error parsing

    Then allow overrides for people who need them.

    For example:

    const client = new HarborClient({
    apiKey: process.env.HARBOR_API_KEY,
    timeoutMs: 10_000
    });

    That is enough for most users.

    Advanced users can go deeper:

    const client = new HarborClient({
    apiKey: process.env.HARBOR_API_KEY,
    baseUrl: "https://api.internal.example.com",
    timeoutMs: 30_000,
    retries: 3,
    headers: {
    "X-App-Version": "1.4.2"
    }
    });

    The basic path should stay simple.

    The advanced path should stay possible.

    Respect Timeouts, Retries, and Idempotency

    Network calls fail.

    That is not an edge case. That is reality.

    An API client should have a serious answer for timeouts, retries, cancellation, and idempotency.

    Requests should not hang forever. Retry behavior should be conservative and predictable. The client should not blindly retry unsafe operations that might create duplicate records or trigger duplicate actions.

    For read requests, retries are often reasonable.

    For write requests, retries require more care. If the API supports idempotency keys, the client can make this much safer:

    await client.payments.create(
    {
    amount: 5000,
    currency: "USD"
    },
    {
    idempotencyKey: "order_123_payment"
    }
    );

    A developer should not have to become a distributed systems expert just to use your library safely.

    The client cannot remove every network problem, but it can prevent the obvious ones from becoming application bugs.

    Make Authentication Hard to Misuse

    Authentication is one of the first things developers touch, so the client should make it clear and safe.

    Bad authentication design looks like this:

    client.setHeader("Authorization", "Bearer " + token);

    That works, but it pushes too much responsibility onto the developer.

    Prefer something explicit:

    const client = new HarborClient({
    apiKey: process.env.HARBOR_API_KEY
    });

    Or, for OAuth-style flows:

    const client = new HarborClient({
    accessToken
    });

    Avoid encouraging developers to hardcode secrets in examples. Use environment variables in documentation. Make token refresh behavior clear. If the client can help with refresh safely, provide that support. If it cannot, explain what the developer is expected to do.

    Security problems often begin as convenience shortcuts in sample code.

    Do not teach bad habits.

    Naming Matters More Than You Think

    Names are the surface area of your client library.

    Once developers start using them, they become hard to change. Sloppy naming creates long-term friction.

    Use names that match the domain, not names that expose internal implementation details.

    Prefer:

    client.collections.share(collectionId, teamId);

    Over:

    client.collectionPermissions.createMapping(collectionId, teamId);

    Maybe the backend stores a permission mapping. The developer probably does not care. They care that they are sharing a collection with a team.

    Good names reduce mental translation.

    Also be consistent. If you use list, get, create, update, and delete for one resource, do not switch to fetch, retrieve, make, modify, and remove for another unless there is a strong reason.

    Consistency is not boring.

    Consistency is professional.

    Avoid Making Developers Parse Raw Responses

    Sometimes client libraries return the raw HTTP response and call it a day.

    That is not enough.

    Most developers want the useful data. They do not want to manually check status codes, parse JSON, and dig through transport details for every normal call.

    This is annoying:

    const response = await client.users.create(data);
    if (response.status === 201) {
    const body = await response.json();
    console.log(body.data.id);
    }

    This is better:

    const user = await client.users.create(data);
    console.log(user.id);

    That does not mean raw responses are never useful. They are. Headers, rate-limit details, and request IDs can matter.

    So expose them deliberately:

    const result = await client.users.create(data, {
    includeResponse: true
    });
    console.log(result.data.id);
    console.log(result.response.headers.get("x-request-id"));

    Give the common path the cleanest API.

    Give the advanced path enough control.

    Documentation Should Match Real Usage

    API client documentation should be full of real usage patterns.

    Not just method signatures.

    Show developers how to:

    • Initialize the client
    • Authenticate
    • Create a resource
    • List resources
    • Handle pagination
    • Catch errors
    • Configure timeouts
    • Use retries
    • Upload files
    • Work with webhooks
    • Test code that uses the client

    Examples should look like code someone would actually put in an app.

    Do not write docs that assume the developer already knows the answer. The whole point is to reduce uncertainty.

    Also keep the docs close to the code. If the library changes and the docs lag behind, trust erodes quickly.

    A wrong example is worse than no example because it wastes the developer’s time while pretending to help.

    Testing the Client Is Not Optional

    A client library needs its own tests.

    Not just tests for the API.

    The client has behavior: serialization, deserialization, authentication, retries, error mapping, pagination, file uploads, streaming, cancellation, and configuration.

    Those things break.

    Tests should cover both happy paths and ugly paths. Mock the transport layer. Test malformed responses. Test failed requests. Test retry behavior. Test that errors contain the right data. Test that optional fields are handled correctly.

    Also consider contract tests against a real or sandbox API.

    A client library is a promise. Tests help you keep it.

    Version Carefully

    Breaking changes in API clients are especially painful because they spread into other people’s codebases.

    Do not rename methods casually. Do not change return shapes without a migration path. Do not remove fields just because the underlying API changed.

    Use semantic versioning responsibly. Deprecate old methods before removing them. Provide clear migration guides.

    Bad versioning teaches developers not to upgrade.

    Once that happens, you end up supporting ancient versions forever because users are afraid the latest one will break everything.

    A stable client library earns trust over time.

    Logs and Debugging Help Matter

    When an API call fails, developers need visibility.

    A good client should make debugging possible without making normal usage noisy.

    Consider supporting a debug mode:

    const client = new HarborClient({
    apiKey: process.env.HARBOR_API_KEY,
    debug: true
    });

    Or a logger hook:

    const client = new HarborClient({
    apiKey: process.env.HARBOR_API_KEY,
    logger: console
    });

    Be careful not to log secrets. Redact authorization headers, tokens, cookies, and sensitive payloads by default.

    Debugging tools should help developers understand what happened without creating security risks.

    That balance matters.

    Make the Library Feel Maintained

    Developers can tell when a library feels abandoned.

    Old dependencies. Broken examples. Unanswered issues. No changelog. No recent release. No support for modern runtimes. Incomplete TypeScript types. Installation warnings. Deprecated packages.

    These details damage confidence before the developer writes a single line of code.

    A loved API client feels alive.

    That does not mean constant churn. It means steady maintenance. Clear changelogs. Compatibility notes. Bug fixes. Runtime support. Honest documentation.

    If the API is important, the client should be treated as important too.

    Final Thoughts

    Writing an API client that developers love is not about wrapping endpoints as quickly as possible.

    It is about reducing friction.

    The library should make the API feel natural, reliable, and safe in the developer’s language of choice. It should provide strong types where appropriate, helpful errors, clean pagination, sensible defaults, safe authentication, careful retries, useful documentation, and escape hatches for advanced cases.

    The best API clients do not make developers think, “I am calling an HTTP endpoint.”

    They make developers think, “This library does exactly what I expected.”

    That feeling is not accidental.

    It comes from caring about the details.