An API without a machine-readable contract is a rumor. The OpenAPI 3.1 specification at GET /api/v1/openapi is the yas.sh API's source of truth: every endpoint, schema, security scheme, and error format, in one document. This guide shows three ways to consume it — generated clients, hand-written requests, and the human reference — and when each is right.
The spec itself
curl -s https://yas.sh/api/v1/openapi | jq '.info, .paths | keys'
The spec covers the full surface: auth (register, login, me, logout), links (CRUD, duplicate check, CSV export), analytics (overview, per-link), API keys, QR generation, tools, and the redirect contract. Two details worth noting:
- Security schemes:
cookieAuth(theyas_sessioncookie) andbearerAuth(yas_live_...API keys) are declared per-endpoint, so generated clients know exactly what each call needs. - Errors: every endpoint documents the RFC 9457
application/problem+jsonformat with thecode,title,status,detail, andretryAfterfields clients should handle.
Option 1 — Generated clients (TypeScript)
The fastest path to a typed client:
npx openapi-typescript https://yas.sh/api/v1/openapi -o yas-api.d.ts
This produces types for every request/response in the API:
import type { paths } from "./yas-api.d.ts";
// Fully typed fetch helper (or use openapi-fetch)
type CreateLink = paths["/links"]["post"]["requestBody"]["content"]["application/json"];
const body: CreateLink = { originalUrl: "https://example.com", customAlias: "launch" };
const res = await fetch("https://yas.sh/api/v1/links", {
method: "POST",
headers: { Authorization: "Bearer " + process.env.YAS_KEY, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
The win: when the API evolves, npx openapi-typescript re-runs and the compiler finds every integration that needs attention. Spec-driven development keeps clients honest.
Option 2 — Hand-written requests
For scripts and one-offs, plain HTTP is all you need. The API reference documents each endpoint with curl, JavaScript, and TypeScript examples:
# create a link — curl
curl -X POST https://yas.sh/api/v1/links \
-H "Authorization: Bearer yas_live_..." -H "Content-Type: application/json" \
-d '{"originalUrl":"https://example.com","customAlias":"launch"}'
// list links — TypeScript
const res = await fetch("https://yas.sh/api/v1/links?limit=20", {
headers: { Authorization: "Bearer " + process.env.YAS_KEY },
});
const { data, nextCursor } = await res.json();
The patterns that matter in any client: send Content-Type: application/json on writes, set Authorization: Bearer <key>, include an Idempotency-Key for retries, and handle 429 with retryAfter (rate limits).
Option 3 — The human reference
The rendered documentation at /docs/api walks every endpoint with the same examples, plus the operational details that specs don't capture: which endpoints are public, which are rate-limited, and the exact error codes with their meanings. When the spec and the page disagree, the spec wins — and they're maintained together, so they don't disagree.
Keeping integrations in sync
The discipline that prevents drift:
- Pin the spec — cache a copy in your repo (
curl -o spec.json https://yas.sh/api/v1/openapi) so builds are reproducible. - Regenerate on upgrade — re-run openapi-typescript whenever you bump integration code; let the compiler find breaking changes.
- Test against the contract — a smoke test that hits the documented endpoints (like the platform's own smoke suite) catches drift before users do.
- Watch the changelog — the changelog notes API changes in each release.
Option 3 — the human reference
Not every interaction needs a generated client. For dashboards, incident response, and one-off debugging, the rendered API reference at /docs/api is the fastest way to find an endpoint's exact request and response shape, its auth requirements, and its error codes. Use it alongside the spec: the reference is readable documentation; the OpenAPI document is the machine contract that guarantees the docs and the code never drift.
Keeping your client in sync with the spec
A generated client is only as good as how often you regenerate it. When the API adds a field, a new endpoint, or changes a schema, your stale client types will silently be wrong. The fix is to make regeneration part of your workflow:
- Run
npx openapi-typescript https://yas.sh/api/v1/openapi -o yas-api.d.tsin CI or on a schedule. - Fail the build if the generated types diverge from what your code expects — the compiler is your change detector.
- Keep the generated file out of hand edits so it always mirrors the spec exactly.
Spec-driven development means the OpenAPI document is the source of truth, your client is derived from it, and your integration is validated against it. That loop catches breakage at compile time instead of at runtime.
Working with the error contract
A robust client does not just handle success. Every endpoint documents the application/problem+json error format with code, title, status, detail, and — on rate limits — retryAfter. Build a single error-handling layer that parses this shape once and maps it to typed exceptions. That way every call site handles errors consistently, logs the code, and reacts correctly to the 429 with retryAfter (see the rate limits guide). Handling the documented error contract uniformly is what separates a client that survives production from one that breaks on the first unexpected response.
Example: a typed update flow
// openapi-fetch or raw fetch — both stay typed against yas-api.d.ts
const { data, error } = await client.PATCH("/links/{id}", {
params: { path: { id: linkId }, body: { title: "Updated" } },
});
if (error) throw new ApiError(error.code, error.detail);
console.log(data.shortCode);
With the generated types, invalid payloads are caught by the compiler before they ever reach the network, and the error contract turns failures into typed, actionable exceptions. This is the practical payoff of keeping a machine-readable contract at the center of your integration.
Quick decision guide
Choose a generated client when you are building a larger integration that will evolve with the API and you want compile-time safety. Choose hand-written requests for one-off scripts, debugging, and quick automation where pulling in a generator is overhead. Choose the human reference when you are reading documentation to understand behavior. Most teams use all three at different points; the OpenAPI document is what keeps them consistent with each other and with the actual API.
Adopting the OpenAPI contract as the single source of truth pays off the first time the API evolves: you regenerate, the compiler surfaces every call site, and your integration stays correct without a manual audit. That guarantee is the real value of spec-driven development.
And when you are unsure which path fits, start with the human reference to understand the endpoint, then decide whether a one-off script or a generated client is warranted. The contract stays the same either way.
Whether you generate, hand-write, or read, the OpenAPI document keeps all three paths in agreement with the live API.
Conclusion
The OpenAPI spec turns the yas.sh API from documented into provable: generated types, contract-tested integrations, and examples that match the implementation. Start with the spec endpoint, skim the human reference, and generate your first typed client this afternoon.
