A partner API that holds its SLA is designed around the promise, not retrofitted to it. The contract covers far more than uptime: stable error formats, a written versioning and deprecation policy, rate limits announced in headers, and webhooks that actually deliver. You hold the promise by measuring internal SLOs stricter than what you sign, and by knowing which partner is degraded before they open a ticket. Here is the method we apply when we design partner APIs.
Why is a partner API a contract, not just an endpoint?
Because partners build revenue on top of it. The moment a third party writes code against your API, everything observable becomes the contract: response shapes, error formats, status codes, rate limits, latency. Uptime is one clause among many. An API that quietly changes the meaning of a field breaks integrations as surely as an outage does.
Three non-negotiable rules follow. Changes are additive: new fields, new endpoints, new enum values behind a version, never a modified type or a field repurposed for something else. Deprecations get a written window (twelve months is a common floor for partner-facing endpoints) with reminders based on actual usage, not a blog post and a shrug. And the versioning policy is published before the first partner signs, because it is far harder to introduce one afterwards. This discipline sits at the core of our API development work: the boring clauses of the contract are the ones that keep it alive.
SLO before SLA: what should you actually promise?
Promise less than you measure. Define internal service level objectives (SLOs) for availability, latency and error rate, observe them over at least a full quarter, then sign an SLA one notch looser. If your platform sustains a given level internally, committing to slightly less externally leaves room for a bad week without a breach.
The gap between the two numbers is your error budget, and it should drive engineering decisions: when the budget burns fast, risky deploys wait. Do the arithmetic before signing anything: large public APIs commonly publish monthly commitments around 99.9%, and even that figure allows roughly 43 minutes of downtime per month. Partners read the remedy clauses; read the measurement clauses instead, because how and where availability is measured matters as much as the number itself.
| SLO (internal) | SLA (contractual) | |
|---|---|---|
| Audience | Your engineers | Partners and their lawyers |
| Purpose | Early warning, error budget | Remedies and credits |
| Strictness | Stricter than the SLA | Looser, with headroom |
| Measured by | Your own telemetry, per partner | A method written into the contract |
| When missed | Freeze risky changes | Service credits, escalation, churn |
How do you make errors predictable?
Every error a partner can receive should be enumerable in your documentation: a typed code, a stable envelope, and a machine-readable signal saying whether a retry makes sense. Naked 500s with an HTML body are the fastest way to lose trust, because the partner's on-call engineer cannot tell your bug from theirs.
Pick one envelope and never change its shape. RFC 9457 (problem details for HTTP APIs) is a solid base; a custom envelope works too, as long as it is versioned with the API. Use HTTP status codes for the transport-level truth and your typed code for the business-level truth:
{
"error": {
"type": "invalid_request",
"code": "AMOUNT_BELOW_MINIMUM",
"message": "amount must be at least 100 minor units",
"retryable": false,
"request_id": "req_9f2c81d4",
"doc_url": "https://api.example.com/docs/errors#AMOUNT_BELOW_MINIMUM"
}
}
Two details pay for themselves: a request_id echoed in every response makes support conversations short, and a Retry-After header on 429 and 503 responses turns partner retry storms into polite backoff.
Does your partner API promise more than it measures? Describe your API: a one-page diagnosis within 48 hours.
Get my diagnosis →Rate limits, load shedding and fairness between partners
Publish the budget, announce consumption in headers, and isolate partners from one another. A rate limit nobody can observe is a trap. A shared pool where one partner can starve the rest is worse. Per-partner quotas plus a global load-shedding threshold keep one noisy integration from degrading the whole platform.
The X-RateLimit-* convention remains the most widely deployed; the IETF has been working on standard RateLimit header fields, still a draft at the time of writing. Whichever you choose, send the headers on every response, not only on 429:
# Sent on every response, not only on 429
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 480
X-RateLimit-Reset: 1767225600
# When the budget is exhausted
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Behind a managed gateway, use its native throttling: Amazon API Gateway, for example, supports per-key usage plans, which map cleanly to per-partner quotas. Under real overload, shed load deliberately: reject early with 429 or 503 plus Retry-After, and protect the partners who are inside their quota before serving the one who is not.
Idempotency and pagination that survive growth
Accept an idempotency key on every mutating endpoint, and paginate every list endpoint by cursor from day one. Partners retry: networks fail, queues redeliver, frameworks time out and resend. Without idempotency keys, a retried POST creates a duplicate; without cursors, offset pagination degrades and skips rows as tables grow.
The mechanics are well understood: the client sends a unique key, the server stores the first response against it and replays that response for any retry within a retention window. Stripe's idempotent requests documentation describes the pattern well. For pagination, return an opaque cursor, document the maximum page size, and guarantee that filters stay stable across pages of the same query.
Who is degraded? Observability per partner
Global dashboards can show a healthy API while your most important partner is failing. Tag every request with the partner identifier and derive per-partner error rates, latency percentiles and quota consumption. The question your monitoring must answer is not "is the API up" but "which partner is having a bad hour, and why".
Per-partner views change incident response: you alert on breach of the partner-level SLO, you notify affected partners before they notice, and your status page reflects reality instead of a green wall. Publish incident timelines and post-incident summaries; partners forgive an outage far more easily than they forgive silence.
Webhooks are part of the product
Outbound webhooks carry the same contractual weight as inbound endpoints: signed payloads, a documented retry schedule with backoff, an event catalogue that only grows, and a redelivery tool partners can trigger themselves. If the SLA covers your API but your webhooks drop events silently, the SLA is fiction.
Treat delivery as an asynchronous pipeline, with a queue between event production and dispatch, so a slow partner endpoint never blocks the rest: that is the same reasoning we detail in our article on event-driven architecture with Lambda and SQS. Stripe's webhooks documentation is a useful public reference for retry windows, signatures and ordering caveats, whatever stack you run.
Partner API design checklist
- Versioning and deprecation policy written and published before the first partner integrates.
- Additive changes only; a field's type or meaning is never modified in place.
- Internal SLOs stricter than the contractual SLA, measured per partner.
- One error envelope, typed error codes, request IDs, no naked 500s.
Retry-Afteron 429 and 503; rate-limit headers on every response.- Idempotency keys on all mutating endpoints, with a documented retention window.
- Cursor pagination and stable filtering from the first release.
- Per-partner dashboards and alerting on partner-level SLO breach.
- Status page, incident communication and post-incident summaries.
- Webhooks signed, retried with backoff, replayable on demand.