Wiring a third party API by contract: capability first, endpoint second

Wiring a third party API starts by saying the capability you need. Name the capability before you pick an SDK, then voice auth, payload, and retry rules. Test the endpoint against the vendor's own example and wrap it in an adapter..

Voiskey Voiskey Team
Capability FirstAPI ContractAdapter Pattern

The Vendor Page You Skim and Regret After Midnight

The classic third-party integration begins the same way every time: the vendor's docs page loads, it is a long scroll of SDK examples, authentication flows, and webhook payloads, and the integration needs one thing done by Friday. So you skim, you copy the example that looks closest, and the copy works in the sandbox, and then at midnight the payment fails for the customer whose token is formatted differently than the example. The regret is not about the code, it is about the reading: you read the docs at the wrong abstraction level, looking for a shape that matches a library rather than a capability that matches the business, and the fix is to reverse that order before wiring a third party API at all.

The skimming starts from a misidentification of the task, and the misidentification is the reason wiring a third party API feels harder than its documentation suggests. The task is not to learn a vendor's API; it is to make one of its capabilities serve your product, and those are different jobs. The documentation is organized around the SDK, and the mind therefore searches for the SDK, and the search is what produces the copy-pasted example with the half-examined defaults. When the integration is wiring a third party API into an existing service, the reading order should follow the business first: what does the customer need to do, what does the service need to know, and what can go wrong. Every vendor page can answer those three questions if they are asked first.

There is an attention asymmetry in vendor docs that makes skimming worse: everything looks equally important, the auth to the end, the webhook signature, the rate limit table, the sandbox modes, and the mind treats a page of equal-weight information as a page of none. Voice changes the physics of the reading, because you can speak the capability you are after and let the agent pull just the matching sections from the docs, then read the relevant paragraphs aloud while skimming is not possible. Speech is inherently sequential, and the sequence forces the relevance filter that the scrolling eye refuses to apply.

The midnight regret has a common cause worth naming: the example worked because it used the vendor's happy-path values, and the customer's values are not happy-path. The token with the odd casing, the phone number with the leading plus, the record that was created by a different provider and migrated in. The integration that survives midnight is the one whose author asked, in advance, where these values are validated, and asked it of the docs in the format a searchable conversation supports. The vendor page is the syllabus, not the lesson; the lesson is taught by the first real payload, and the syllabus is only useful when read for the specific lesson.

Naming the Capabilities You Need, Not the SDK You Found

Before opening the vendor docs a second time, say the capabilities aloud, in the language of your product rather than the language of the library: I need to verify that a bank card is chargeable, and I need to charge an amount and know whether it succeeded, and I need the charge to fail loudly if the remote service is slow. Three sentences, and the three sentences are the real specification. The capability list is the filter that the docs will be read against, and it is also the answer to the question the integration usually answers wrong, which is that the SDK is the goal. The SDK is a convenience; the capability is the contract, and the capability-first framing is the entire method.

The naming pass also exposes the capabilities that do not need the vendor at all, which is the cheapest finding in this work: if the requirement is to remember that a customer preferred a delivery window, the service might need a column rather than a provider. Say each requirement and check it against the capability list, and the ones that survive are the ones worth wiring. The filter is where the abstraction level is set, and the abstraction level is what decides whether the integration is forty lines and sturdy or four hundred lines and fragile. Product words produce product-size integrations; SDK words produce integration-shaped archaeology.

When the capabilities are named, find the vendor features that match each one and say the mapping: verifying a card is the authorization call, charging is the capture call, and failing loudly means the timeout is a provider-configurable parameter and not the SDK's silent default. The mapping is the moment the docs become usable, because the search is now for three named things rather than one plausible example, and each named thing can be verified in isolation. Ask the agent to quote the doc lines for each mapping and read them aloud, since the quoted lines are the evidence that the mapping is real and the doc is being read rather than being imagined.

The capability-first approach has a social payoff: it makes the integration's scope discussable. A teammate can say, we need the charge capability but not the refund capability yet, and the sentence maps directly onto the adapter you will build, while the SDK-first version has no such boundary, since a bundled SDK imports its whole surface. The scope sentence, spoken once, is what saves the integration from the creeping completeness that vendors are designed to encourage. Naming the capabilities you need is the difference between using a provider and subscribing to it, and it is a difference measured in the lines that stay in your codebase.

Voicing Auth, Payload, and Retry Rules as a Contract

Every integration is a contract with three clauses, and they are worth stating aloud before any code: the auth clause, which says how the service identifies itself and what it does in the token expiry; the payload clause, which says what each request carries, in whose units, and what the response shape means; and the retry clause, which says when the service may try again, how many times, and what it does when the tries are spent. Say the three clauses in plain sentences and let the agent turn them into the configuration, and the spoken contract is what the vendor page, the code, and the next engineer can all be checked against, which the unspoken contract can never be.

The auth clause is the one that most often fails after midnight, so voice it with the edges: the token comes from the secrets store and is valid for fifteen minutes, and the service must refresh it before the retry, not after the failure. The edge is the refresh timing, and the edge is exactly what the vendor example skips, since the example refreshes lazily and the example's traffic is light. Dictate the edge and confirm the agent's implementation reads it back the same way: refresh before the first retry, and if a token is stale, the integration returns a precise error instead of a confusing one. The precise error is the contract's guarantee to the future engineer who will copy the pattern.

The payload clause benefits the most from being spoken, because payloads are where naming disagreements live: the vendor calls it cents and the product calls it amount, the vendor expects the country code separately and the customer service has it combined. Say the mapping out loud as you build it: the charge request sends the amount in minor units, which are cents, and the currency code, and the customer id only as the merchant reference. The spoken mapping is the documentation that the adapter will carry, and it is the thing that makes the next payload change a ten-minute edit rather than a deciphering session on a support call. The contract is a conversation first, and a conversation is the format humans can argue with.

The retry clause is the policy question, and it deserves a decision rather than a default: the service retries the charge twice on timeouts, never on a declined response, and pauses between retries. Say the policy and the reason, because the reason is what keeps the policy alive: never retrying a declined charge is the part that prevents the double charge, and it is the part a default library might get wrong if the error classes are guessed. Read the agent's retry configuration back as a sentence and compare it against the policy, since the configuration is the contract's implementation and a mismatch there is the silent disaster, the one that the mid-afternoon test cage does not catch and the customer on the weekend does.

Testing the Wired Endpoint Against the Vendor's Own Example

The vendor's example is the ground truth of the contract because it is the only payload the vendor guarantees to understand, so test the wired endpoint against it before testing anything you invented. Send the vendor's classic example through your method, read your logging output, and confirm the request matches the doc's expected shape: the field names, the units, the nonce, the signature. The confirmation is the point of the test, since a wire that passes your own payload but mangles the vendor's units is a wire that will betray the first real customer. The example is the calibration standard, and it costs three minutes to run and ninety seconds to narrate the result.

Then test the endpoint with the payloads that are yours and not the vendor's, and say each variant as you send it: the amount with the trailing zeros, the name with the apostrophe, the customer id that begins with a letter. The variants are the contract's validation, and the vendor's responses will fall into two groups, the ones accepted and the ones rejected with a message, and both groups are information. The rejected variants tell you where your validation must live, and the accepted variants tell you where the vendor's schemas are looser than the docs claim. The narration of the variants is the record that prevents the next engineer from re-running the same fifteen experiments by hand.

The unhappy test that proves the contract is the deliberate fault injection: kill the network mid-request, feed a malformed signature, send the token a minute after expiry, and speak the outcome of each: the malformed signature returns the vendor's error code and our error mapping picks it up, the expired token produces the precise error we defined, and the network kill retried once and then raised the timeout we specified. The three faults are the contract's edges, and the test that runs them is the answer to the question that the vendor page never answers: what does this integration do when the world does not cooperate? The integration that passes the fault run is the one that can be merged on a Friday.

The final calibration point is the vendor's own test mode, if it exists, because the vendor's sandbox is the only environment where the vendor's assumptions are guaranteed to hold. Run the whole happy flow there, talk through what you see, and then compare the sandbox output with the output of your own test doubles. The comparison is the honesty check: if the vendor's sandbox accepts something your test double rejected, your double has become the fiction, and the contract has two versions. The vendor's environment is the arbiter, and the arbiter has the final word before the integration graduates to the code review.

Wrapping the Vendor API Behind an Adapter You Control

The last act of the integration is the wedge: never call the vendor's SDK from the service's business code, wrap it. The adapter is the boundary, and the adapter's interface is written in your language: chargeCard(amount, currency, reference) returns a result, and the interface says nothing about the vendor, the tokens, or the payload mapping, because those details live inside the adapter where they belong. The spoken reason for the wedge is the vendor's own behavior, since providers change their schemas, their deprecation lists, and their pricing, and the code that touches the vendor in a hundred places is the code that takes a hundred edits to survive a deprecation, while the code that touches the vendor in one place takes one.

The adapter also owns the error translation, which is the part that makes the rest of the codebase calm: the vendor's cryptic code becomes the service's own error, charge declined, card expired, provider timeout, and each translation is one spoken sentence. Dictate the mapping so the reason is recorded: the provider's declined code six becomes our card declined, and the timeout category becomes our retry exhausted, and the unresolved codes fall into the generic provider error bucket. The translation table is the contract's human face, and the face determines how the next four engineers will treat the integration, since an error that reads like your product gets handled like your product and an error that leaks the vendor's code gets handled with a shrug.

The adapter is also where the retry and the rate limiting live, and where the observability gets attached: each call carries a correlation id, and the adapter logs the vendor's request id with it, so the support conversation can be traced across both sides. Say the observability requirement aloud before the adapter is written: every call logs the vendor request id, the elapsed time, and our cached correlation, and the log line is one per call, no verbose request dumps. The one-per-call rule is the policy that keeps the logs useful, while the verbose dump is the policy that eats the disk and the debugging afternoon. The adapter is the place where the policy lives in code instead of in the memory of the person who built it.

The adapter is paid for the moment the vendor moves, and the payment is the reason the wedge is non-negotiable: the vendor changes the signature algorithm, or the SDK is rotated, and the work is one file and one spoken session rather than a sweep across the service. The adapter's interface stays, the tests stay, and the diff is contained, which is the entire definition of wrapping the vendor API behind an adapter you control. The integration is then not a relationship with a provider but a decision in your codebase, and the decision can be revisited on your schedule rather than the vendor's, which is the only position a stable service can afford to be in.

FAQ s

Name the capabilities your product needs, in product language, before opening the vendor docs: verify a card, charge an amount, fail loudly on slowness. Then map each capability to the vendor's specific feature. Capability-first keeps the integration at product size, while SDK-first reading produces archaeology and the famous copy-pasted example.

Never inherits them silently. Decide the policy out loud: retry twice on timeouts, never on a declined response, with a pause between retries, and read the agent's configuration back as a sentence. The declined-never-retried rule is what prevents double charges, and it is the rule that default library behavior often guesses wrong.

Calibrate against the vendor's own example payload first, then run your real-shaped variants, then deliberately inject faults: network kill, malformed signature, expired token. Finally run the happy flow in the vendor's sandbox and compare it with your test doubles. The sandbox is the arbiter, and its verdict overrides the fictions in your doubles.

Because providers change. A single adapter turns a vendor schema change into a one-file fix, it owns the error translation that makes the rest of the service calm, and it carries the retry policy and the correlation logging in one place. Direct SDK calls spread the vendor's vocabulary through your business code, and every deprecation then costs a sweep.

Say less. Get more
Say less. Get more
Say less. Get more
Say less. Get more
VoiskeyVoiskey

Your voice, amplified.

Name the capability first, voice the three clauses, and wrap the vendor behind your adapter.

Use it across macOS, Windows, iOS, and Android

Use it across