You are probably here for one of two reasons: an integration that keeps breaking, or a product that has outgrown the backend someone built quickly two years ago. Both come down to design decisions that are cheap this week and expensive once a partner depends on them. Here is how we approach API work, what we deliberately refuse, and how a two-person team in France runs an international project entirely in writing.
Six decisions you cannot undo once someone integrates
Six things get decided in the first two weeks of an API project and quietly govern everything after it. Resource modeling: expose your database tables as endpoints and every schema change becomes a breaking change for consumers. Versioning: choose a strategy before the first integration, because retrofitting a v1 prefix onto live traffic means running two code paths for months. Pagination: offset and limit looks fine in a demo, then starts skipping and duplicating rows the moment records are inserted while a client is paging, which is why cursor-based pagination is the default for anything ordered by time. Error semantics: a 200 response carrying an error object in the body breaks every HTTP client, retry policy and monitoring tool downstream. Use real status codes and a consistent envelope (RFC 9457 problem details is a sane default) with a stable machine-readable code, not just a human message someone will reword next quarter. Idempotency: any endpoint that moves money, sends mail or creates a record needs an idempotency key, because clients retry and mobile networks drop responses after the write has already committed. Authentication: session cookies, API keys, OAuth clients and signed service tokens are not interchangeable, and swapping one for another after partners have shipped is a migration project with a communication plan attached. None of this is hard. It is just expensive to change once somebody else depends on it.
Documentation is a deliverable, not a wiki page that rots
Handwritten API docs go stale faster than anyone plans for. Someone adds an optional field, someone else tightens a validation rule, and the page nobody owns keeps describing last quarter's contract. The fix is structural, not disciplinary: generate the OpenAPI specification from the same schemas the server validates against, so the spec cannot describe an endpoint the code does not implement. In a TypeScript and Node stack that usually means Zod schemas compiled to the spec at build time, with the types shared into the client. Drift then shows up as a failing build instead of a support thread six weeks later.
Around the spec, the things integrators actually need: request and response examples with real payloads including the error cases, because the happy path is never what blocks them. Explicit auth instructions covering token acquisition, expiry and rotation. Documented rate limit headers. A changelog with dates, so a partner debugging a regression can see what moved. Test data or a sandbox environment that behaves like production rather than a stub that always returns success.
The honest metric is how many questions reach you. If a competent developer can integrate without emailing anyone, the documentation is finished. If they need a call to understand your auth flow, the documentation is the problem, and bolting a call onto it would only hide that.
Rate limits, observability, and the parts that only fail in production
The gap between a demo API and one that partners build on is almost entirely operational. Rate limiting first: fixed window counters allow a double burst across the window boundary, so a token bucket or a sliding window is usually the better shape. Limit per API key and per account, not per IP, because corporate NAT can put an entire office behind one address. Return 429 with Retry-After and expose the remaining quota in headers, so well-behaved clients back off instead of hammering you into a real outage.
Then observability, which means being able to answer questions you did not anticipate. Structured JSON logs with a request ID propagated through every downstream call. Latency tracked at p95 and p99, never as an average, because averages hide exactly the tail that makes a partner complain. Error rate broken down per endpoint and per consumer, so you can tell one badly behaved caller from a deploy that broke everyone.
What actually takes serverless APIs down is rarely application code: it is Postgres connection exhaustion, because every function instance opens its own connection under load. That means a pooler in transaction mode, and knowing what it costs you (prepared statements, session state, LISTEN and NOTIFY). Worth noting too that platforms like Vercel or Supabase are metered by usage, invocations, compute time and bandwidth, rather than sold at a flat seat price, and the tiers change often, so an unbounded endpoint is a billing incident as well as an availability one.
Webhooks and outbound calls: assume the vendor will lie to you
Most integration work is defensive engineering. Every outbound call needs an explicit timeout, because several popular HTTP clients wait forever by default and one slow vendor will saturate your worker pool. Retries need exponential backoff with jitter, and a clear split between retryable failures (429, 5xx, timeouts) and permanent ones (400, 422), since retrying a validation error is just a slower way to fail. A circuit breaker stops a degraded vendor from taking your API down with it. If the call creates something on their side, send an idempotency key so your own retry does not double-charge a customer.
Webhooks deserve their own paranoia. Delivery is at-least-once, so handlers must be idempotent on the event ID and safe to replay. Verify the signature and reject stale timestamps, or a captured payload can simply be replayed at you. Events arrive out of order more often than vendors admit, so persist the raw payload, treat it as a notification rather than as truth, and fetch current state from the API before acting on anything financial. Keep a dead-letter queue and a replay tool, because deliveries will be dropped during your own deploys.
And vendors change without telling you: fields appear, enums gain values, the sandbox behaves unlike production. A nightly reconciliation job comparing your records against theirs catches the divergence before your finance team does.
When a public API is the wrong answer, and when not to hire us
Plenty of clients ask for a public API and need something smaller. If your only consumer is your own frontend, a typed internal contract (tRPC, or a client generated from your schema) ships faster and refactors freely, because you control both ends and owe nobody a deprecation notice. If you have two known partners, two scoped endpoints and a rotating shared secret beat a versioned product with docs, quotas, a sandbox and a support burden. GraphQL earns its keep when many different clients need different shapes of the same data, or when a mobile team is fighting over-fetching on a bad network; for one consumer and a small team it mostly buys a resolver layer, harder HTTP caching and fresh N+1 risks. A public API is a commitment to strangers, and it pays off when integrations are the business model, not when they are hypothetical.
Where you should hire someone else. Regulated infrastructure needing on-premises deployment, a HIPAA BAA or FedRAMP authorization. Round-the-clock contractual on-call with minute-level response SLAs: two people in one time zone cannot honestly promise that. Existing Java, .NET or Go estates, which are not our stack. Heavy data engineering, streaming pipelines and warehouse modeling. Latency budgets in single-digit milliseconds, trading adjacent or otherwise. Work that needs six engineers in parallel next month, because we do not staff up. And anyone whose process runs on stand-ups, workshops or a weekly call: we do not do those, and pretending otherwise would waste your quarter and ours.
Two people, one office in Dijon, and a client eight time zones away
We are two founders. Robin Monteiro writes the code (TypeScript, Node, Next.js, Postgres) and Florian Loppion handles the digital marketing side. One office, at 9 rue Jean-Jacques Rousseau in Dijon, France. No branch abroad, no account manager, no delivery lead relaying your question to whoever is free. You talk to the person typing, which is the real upside of hiring a small team and the fair side of the trade against a large agency.
Everything happens in writing, by email, with a reply inside 24 business hours. That one constraint removes the international problem outright: a client in California or Bangalore never books a slot, never rearranges a morning, never takes a 3am call to review pagination. Decisions land in a thread that is still searchable a year later, when a new developer asks why the error format looks the way it does.
What you have to do differently. Write questions with enough context to be answered in one pass, because a one-line message costs two round trips. Grant repository, staging and vendor dashboard access early, read-only where that is enough. Name one person who can decide, since consensus by email is slow. Expect written reviews and pull requests rather than a live demo where objections get lost. Our named references are French SMEs (Chouchou Ribeyre, Au Petit Detail, LB Athletic, Mediavocats, Vectosolve) and the process is identical for a client abroad. You own the code, the cloud accounts and the domain throughout.