Developers
FY9 API
Create short links and QR codes from your own systems and read their analytics. JSON over HTTPS, one key per integration.
Quick start
- API access is included in the Business and Agency plans (and in legacy plans that had it). Create a key under Developers in your dashboard and choose its scopes.
- Send it as a bearer token. The key is shown once; FY9 stores only a hash.
- Create a link:
curl -X POST https://fy9.in/api/v1/links \
-H "Authorization: Bearer fy9_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-link" \
-d '{"destination":"https://example.com/offer","alias":"offer-1042","kind":"dynamic","tags":["email"]}'{
"data": {
"id": 98213,
"code": "offer-1042",
"shortUrl": "https://fy9.in/offer-1042",
"kind": "dynamic",
"status": "active",
"destination": "https://example.com/offer",
"tags": ["email"],
"visitCount": 0,
"createdAt": "2026-10-18T09:12:44Z"
}
}const res = await fetch("https://fy9.in/api/v1/links", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.FY9_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ destination: "https://example.com/offer" }),
});
if (!res.ok) throw new Error((await res.json()).detail);
const { data } = await res.json();
console.log(data.shortUrl);Authentication
Every request needs Authorization: Bearer fy9_…. Keys belong to one workspace and carry scopes:
links:read— list and read linkslinks:write— create and change linksqr:read,qr:write— read and create QR codesanalytics:read— read link analytics
Rotating a key issues a new secret and keeps the old one working for 24 hours. Revoked or expired keys get 401 invalid_api_key. A key without the needed scope gets 403 insufficient_scope. Keep keys on your server; never put them in web pages or apps.
Credits and limits
Operations that create or change something use one credit each: POST /links, PATCH /links/{id}, POST /qr-codes and the legacy /api/GenerateUrl. Reading data is free. Redirect visits never use credits.
- Your plan includes a monthly allowance (Business 5,000; Agency 25,000), reset on the 1st of each month in India time.
- Credit packs (10,000, 100,000 or 1,000,000 operations) are used after the allowance, earliest expiry first, and last 365 days.
- A request that fails (validation error, conflict, server error) gives its credit back. A retry with the same
Idempotency-Keyis never charged twice. - When nothing is left you get
402 credits_exhausted. - Rate limit: 120 requests a minute per key. Over the limit you get
429with aRetry-Afterheader.
Links
Create — POST /api/v1/links
Body fields: destination (required, http/https), alias, kind (static or dynamic), title, description, tags, expiresAt, activatesAt, maxVisits, password, utm ({source, medium, campaign, term, content}, merged into the destination), rules (smart routing) and reuseExisting (return an identical existing static link instead of creating a duplicate). Features your plan doesn't include return 402 plan_limit with the plan that includes them.
List — GET /api/v1/links
Query: search, tag, status, kind, sort (created, clicks, title), descending, page, pageSize (max 100). Responses carry meta.total, meta.page and meta.pageSize.
Read and change — GET / PATCH /api/v1/links/{id}
PATCH accepts the fields to change, plus status (active, disabled, archived). Changing the destination of a static link returns 409 static_link: static links never change by design. Dynamic link changes are recorded in the link's history.
QR codes
POST /api/v1/qr-codes with name, kind (static or dynamic), template (url, menu, review, whatsapp, payment, event, vcard), content and an optional style (foreground, background, errorCorrection). Dynamic codes encode https://fy9.in/q/{code} so their visits are reported as scans. Download the vector file from GET /api/v1/qr-codes/{id}.svg. Styles that wouldn't scan reliably (low contrast, light-on-dark) are rejected with an explanation.
Analytics
GET /api/v1/links/{id}/analytics?from=2026-10-01&to=2026-10-18 returns totals, a daily series and breakdowns by country, device, browser, OS, referrer and language. Ranges are limited to your plan's retention (rangeClamped tells you when that happened). Unique visitors are an estimate; the response's uniqueMethod explains how it's calculated. Values FY9 couldn't determine appear as Unknown.
Usage
GET /api/v1/usage returns operations used this month, credits remaining, each credit lot with its expiry, and the list of metered operations.
Errors
Errors are problem details with a stable code, a human-readable detail and sometimes details (for example the field at fault):
{ "status": 400, "title": "Invalid request", "code": "validation",
"detail": "Links to private or local network addresses can't be shortened.",
"details": { "field": "destination", "problem": "PrivateAddress" } }validation(400),invalid_api_key(401),plan_limitorcredits_exhausted(402),insufficient_scopeorforbidden(403),not_found(404),alias_taken,static_link(409),idempotency_conflict(422),rate_limited(429),server_error(500, with atraceIdto quote to support).
Idempotency
Send Idempotency-Key: <your id> (up to 100 characters) on POST requests. Repeating the same request with the same key within 24 hours returns the original response with Idempotent-Replayed: true and isn't charged. Reusing a key with a different body returns 422 idempotency_conflict.
Legacy API
Integrations built for the previous fy9.in API keep working unchanged: POST /api/GenerateUrl (fields longurl, apikey, urlexpiry in minutes, issingleclick), POST /api/GenerateShortUrl?WebUrl=…, GET /api/ClickCountUrl?Url=… and GET /api/GetPackageList. They answer in the same format as before ({ message: "success" | "failure", … }). Legacy keys stay valid for as long as the package they were issued with. New integrations should use /api/v1.
OpenAPI
The machine-readable description generated from the running API is at /api/openapi/v1.json. Import it into Postman, Insomnia or a code generator.