Appearance
Links API
The Links API is the primary way to create and manage affiliate links programmatically. It covers creating individual routed links, reading them back with full CRC traces, updating mutable metadata, deleting links, bulk-creating up to 100 links at once (Enterprise), and getting a routing decision without persisting a link.
Base path: https://api.fyralynx.com/v1
Tier required: Publisher or Enterprise (see per-endpoint notes)
The Link Object
Every Links API response returns one or more Link objects. The full schema:
json
{
"id": "01HX4Z9Y3ABCDEF1234567890AB",
"url": "https://www.amazon.com/dp/B0CRMZHDG4",
"canonicalUrl": "https://www.amazon.com/dp/B0CRMZHDG4",
"routeUrl": "https://go.fyralynx.com/r/abc123",
"title": "Sony WH-1000XM5 Noise Cancelling Headphones",
"preset": "auto",
"merchantId": "amazon",
"networkId": "Impact",
"region": "US",
"confidence": 87,
"passthrough": false,
"deepLinkUsed": true,
"normalizedEpcUsd": 0.043,
"groupId": "grp_electronics_june",
"notes": "Used in YouTube description - pinned comment on WH-1000XM5 video",
"healthStatus": "ok",
"deepLinkStatus": "validated",
"lastHealthCheckAt": "2026-05-16T10:00:00Z",
"createdAt": "2026-05-15T14:30:00Z",
"updatedAt": "2026-05-15T14:30:00Z"
}Field reference
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique link identifier. |
url | string (URI) | Original URL as submitted. |
canonicalUrl | string (URI) | Merchant-normalised URL after canonicalization (ASIN extraction, param stripping, scheme normalisation). Equals url when passthrough: true. |
routeUrl | string (URI) | The go.fyralynx.com/r/… URL to publish in content. Resolves via edge redirect in < 50ms to the winning candidate URL with affiliate tracking params injected. |
title | string | Display title. May be absent if not provided and auto-extraction failed. |
preset | string | Routing preset applied: auto, link-in-bio, or long-form. |
merchantId | string | Merchant identifier (e.g. amazon, flipkart, jumia). |
networkId | string | Affiliate network chosen by the EPC engine. |
region | string | Storefront region of the winning route (e.g. US, IN, NG). |
confidence | number | Routing confidence score 0-100. Green ≥ 80, amber 60-79, red < 60. |
passthrough | boolean | When true, routing is bypassed - the URL is served exactly as provided. |
deepLinkUsed | boolean | Whether an app deep link pattern was applied. |
normalizedEpcUsd | number | Expected earnings per click, normalised to USD. An estimate based on the EPC formula - not a guarantee. |
groupId | string | Collection group ID, if assigned. |
notes | string | Creator's private notes (never visible to audiences). |
healthStatus | string | ok / warning / critical / unknown. |
deepLinkStatus | string | validated / unchecked / invalid / ttl_expired. |
lastHealthCheckAt | string (ISO 8601) | When the most recent health probe ran. |
createdAt | string (ISO 8601) | Link creation timestamp. |
updatedAt | string (ISO 8601) | Last update timestamp. |
The crcTrace object (full routing decision trace) is present on POST /links and GET /links/{id} responses. It is omitted from GET /links list responses for performance.
POST /links - Create a routed link
Canonicalise a merchant URL, run the full EPC routing engine, persist the resulting link, and return it with a go.fyralynx.com routing URL and the complete CRC trace.
Tier: Publisher+
Click counting: One click is counted against the monthly quota each time the returned routeUrl is followed by a real visitor. Simulation and /route endpoint calls are not counted.
Request body
json
{
"url": "https://www.amazon.com/dp/B0CRMZHDG4",
"preset": "auto",
"title": "Sony WH-1000XM5 Headphones",
"passthrough": false,
"country": "US",
"device": "desktop",
"groupId": "grp_electronics_june",
"notes": "Featured in June tech roundup"
}| Field | Type | Required | Description |
|---|---|---|---|
url | string (URI) | ✅ | Merchant product URL. Must be HTTPS from a supported merchant domain. URLs from unsupported domains are accepted but auto-set to passthrough: true. |
preset | string | Routing preset. One of auto, link-in-bio, long-form. Defaults to auto. | |
title | string (max 200) | Display title. Auto-extracted from URL metadata if omitted. | |
passthrough | boolean | When true, bypass all canonicalization and routing. Default: false. | |
country | string (ISO 3166-1 alpha-2) | Anticipated visitor country. Influences region-match and availability signals. | |
device | string | Anticipated visitor device: desktop, ios, or android. Influences device bias signal. Defaults to desktop. | |
groupId | string | Assign this link to a collection group. | |
notes | string (max 1000) | Private creator notes. |
Response - 201 Created
json
{
"data": {
"id": "01HX4Z9Y3ABCDEF1234567890AB",
"url": "https://www.amazon.com/dp/B0CRMZHDG4",
"canonicalUrl": "https://www.amazon.com/dp/B0CRMZHDG4",
"routeUrl": "https://go.fyralynx.com/r/abc123",
"title": "Sony WH-1000XM5 Noise Cancelling Headphones",
"preset": "auto",
"merchantId": "amazon",
"networkId": "Impact",
"region": "US",
"confidence": 87,
"passthrough": false,
"deepLinkUsed": true,
"normalizedEpcUsd": 0.043,
"healthStatus": "ok",
"deepLinkStatus": "validated",
"createdAt": "2026-05-15T14:30:00Z",
"updatedAt": "2026-05-15T14:30:00Z",
"crcTrace": {
"winner": {
"merchantId": "amazon",
"networkId": "Impact",
"region": "US",
"routeScore": 0.0487,
"normalizedEpcUsd": 0.043,
"confidence": 87
},
"alternatives": [
{
"merchantId": "amazon",
"networkId": "CJ",
"region": "US",
"routeScore": 0.0451,
"normalizedEpcUsd": 0.039,
"confidence": 82,
"whyNotChosen": "Impact scored +0.0036 higher due to private deal override (+8%) applied to this network."
}
],
"signals": [
{ "name": "epc_base", "value": 0.035, "label": "Base EPC (Impact/Amazon US)", "source": "essential" },
{ "name": "trend", "value": 0.08, "label": "Short-term EPC trend (EWMA)", "source": "essential" },
{ "name": "deeplink", "value": 0.12, "label": "Deep-link validated", "source": "essential" },
{ "name": "region_match", "value": 0.08, "label": "Region match (US→US)", "source": "essential" },
{ "name": "deal", "value": 0.08, "label": "Private deal override (+8%)", "source": "creator" }
],
"scoringFormula": {
"intermediateScore": 0.0443,
"confidenceFactor": 1.10,
"finalScore": 0.0487
}
}
}
}curl example
bash
curl -X POST https://api.fyralynx.com/v1/links \
-H "Authorization: Bearer $FYRALYNX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.amazon.com/dp/B0CRMZHDG4",
"preset": "auto",
"title": "Sony WH-1000XM5 Headphones"
}'TypeScript example
typescript
interface CreateLinkRequest {
url: string;
preset?: 'auto' | 'link-in-bio' | 'long-form';
title?: string;
passthrough?: boolean;
country?: string;
device?: 'desktop' | 'ios' | 'android';
groupId?: string;
notes?: string;
}
async function createLink(req: CreateLinkRequest): Promise<Link> {
const response = await fetch('https://api.fyralynx.com/v1/links', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.FYRALYNX_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(req),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Fyralynx ${response.status}: ${error.code} - ${error.message}`);
}
const { data } = await response.json();
return data;
}Errors
| Code | Status | Cause |
|---|---|---|
VALIDATION_ERROR | 422 | url is missing, not a valid HTTPS URI, or fails another validation rule. |
UNSUPPORTED_MERCHANT | 422 | URL domain is not a supported Fyralynx merchant. Link is created with passthrough: true. |
CLICK_QUOTA_EXCEEDED | 403 | Monthly click quota reached (Free tier hard cap). |
PUBLISHER_TIER_REQUIRED | 403 | API access requires Publisher or Enterprise plan. |
GET /links - List links
Returns a paginated list of routed links for the authenticated creator, ordered by creation date descending.
Tier: Publisher+
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | integer | 1-based page number. Default: 1. |
limit | integer | Items per page (1-100). Default: 20. |
merchantId | string | Filter by merchant ID (e.g. amazon, flipkart). |
networkId | string | Filter by affiliate network. |
preset | string | Filter by routing preset: auto, link-in-bio, long-form. |
groupId | string | Filter by collection group ID. |
passthrough | boolean | true returns only passthrough links; false returns only routed links. |
createdAfter | string (ISO 8601) | Return links created on or after this datetime. |
createdBefore | string (ISO 8601) | Return links created on or before this datetime. |
Response - 200 OK
json
{
"data": [
{
"id": "01HX4Z9Y3ABCDEF1234567890AB",
"url": "https://www.amazon.com/dp/B0CRMZHDG4",
"canonicalUrl": "https://www.amazon.com/dp/B0CRMZHDG4",
"routeUrl": "https://go.fyralynx.com/r/abc123",
"title": "Sony WH-1000XM5 Headphones",
"preset": "auto",
"merchantId": "amazon",
"networkId": "Impact",
"region": "US",
"confidence": 87,
"passthrough": false,
"deepLinkUsed": true,
"normalizedEpcUsd": 0.043,
"healthStatus": "ok",
"deepLinkStatus": "validated",
"createdAt": "2026-05-15T14:30:00Z",
"updatedAt": "2026-05-15T14:30:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 347,
"totalPages": 18,
"hasNextPage": true,
"hasPrevPage": false
}
}Note: crcTrace is omitted from list responses for performance. Use GET /links/{id} to retrieve the full trace for a specific link.
curl example
bash
# List the 50 most recent Amazon links
curl "https://api.fyralynx.com/v1/links?merchantId=amazon&limit=50" \
-H "Authorization: Bearer $FYRALYNX_API_KEY"GET /links/{id} - Get a link
Returns full link details including the complete CRC trace.
Tier: Publisher+
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | string (UUID) | Link UUID returned by POST /links. |
Response - 200 OK
Same as the POST /links response body, including the full crcTrace object.
curl example
bash
curl "https://api.fyralynx.com/v1/links/01HX4Z9Y3ABCDEF1234567890AB" \
-H "Authorization: Bearer $FYRALYNX_API_KEY"PATCH /links/{id} - Update a link
Partially update a link's mutable metadata. The routing decision itself is not re-evaluated - submit a new POST /links to force a fresh routing computation.
Tier: Publisher+
Request body
At least one field must be provided.
json
{
"title": "Sony WH-1000XM5 - June Campaign",
"preset": "link-in-bio",
"passthrough": false,
"groupId": "grp_june_campaign",
"notes": "Updated for Instagram story campaign"
}| Field | Type | Description |
|---|---|---|
title | string (max 200) | New display title. |
preset | string | New routing preset. Does not re-trigger routing. |
passthrough | boolean | Toggle passthrough mode. |
groupId | string | null | New group ID. Send null to remove from all groups. |
notes | string | null (max 1000) | New notes. Send null to clear. |
Response - 200 OK
Returns the updated link object. crcTrace is included.
curl examples
bash
# Enable passthrough mode
curl -X PATCH "https://api.fyralynx.com/v1/links/01HX4Z9Y3ABCDEF1234567890AB" \
-H "Authorization: Bearer $FYRALYNX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"passthrough": true}'
# Rename and move to a group
curl -X PATCH "https://api.fyralynx.com/v1/links/01HX4Z9Y3ABCDEF1234567890AB" \
-H "Authorization: Bearer $FYRALYNX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Sony Headphones - June Campaign",
"groupId": "grp_june_campaign"
}'DELETE /links/{id} - Delete a link
Soft-deletes a link. The link is no longer returned by GET /links, but its routeUrl (go.fyralynx.com/r/…) continues to resolve for 30 days to avoid breaking published content. After 30 days, the routing URL redirects to the original URL unchanged.
Tier: Publisher+
Response - 204 No Content
No response body.
curl example
bash
curl -X DELETE "https://api.fyralynx.com/v1/links/01HX4Z9Y3ABCDEF1234567890AB" \
-H "Authorization: Bearer $FYRALYNX_API_KEY"POST /links/bulk - Bulk create links
Create up to 100 routed links in a single request. Each item is processed identically to POST /links. The response includes a per-item result array - failed items include an error field; successful items include a link field.
Tier: Enterprise only. Publisher keys receive 403 ENTERPRISE_TIER_REQUIRED.
Request body
json
{
"links": [
{
"url": "https://www.amazon.com/dp/B0CRMZHDG4",
"preset": "auto",
"title": "Sony WH-1000XM5"
},
{
"url": "https://www.flipkart.com/product/p/itmXXXXXXX",
"preset": "auto",
"passthrough": true,
"title": "Sony WH-1000XM5 (Flipkart)"
},
{
"url": "https://www.amazon.co.uk/dp/B0CRMZHDG4",
"preset": "link-in-bio",
"country": "GB"
}
]
}| Field | Type | Constraints |
|---|---|---|
links | array | Min 1 item, max 100 items. |
links[*] | CreateLinkRequest | Same schema as POST /links. Duplicate URLs are rejected per-item. |
Response - 201 Created
json
{
"results": [
{
"index": 0,
"link": {
"id": "01HX4Z9Y…",
"routeUrl": "https://go.fyralynx.com/r/abc123",
"merchantId": "amazon",
"networkId": "Impact",
"region": "US",
"confidence": 87
}
},
{
"index": 1,
"link": {
"id": "01HX4Z9Z…",
"routeUrl": "https://go.fyralynx.com/r/def456",
"merchantId": "flipkart",
"networkId": "Cuelinks",
"region": "IN",
"passthrough": true,
"confidence": 95
}
},
{
"index": 2,
"error": {
"code": "DUPLICATE_URL",
"message": "This URL already exists in your link library."
}
}
],
"summary": {
"total": 3,
"created": 2,
"failed": 1
}
}The response always returns HTTP 201 - even if some items failed. Check each item's error field to identify failures. Failures on individual items do not prevent other items from being processed.
curl example
bash
curl -X POST "https://api.fyralynx.com/v1/links/bulk" \
-H "Authorization: Bearer $FYRALYNX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"links": [
{ "url": "https://www.amazon.com/dp/B0CRMZHDG4", "preset": "auto" },
{ "url": "https://www.amazon.com/dp/B0CH2FBTS5", "preset": "auto" }
]
}'TypeScript - batch helper
typescript
async function createLinksInBatches(urls: string[]): Promise<Link[]> {
const BATCH_SIZE = 100;
const results: Link[] = [];
for (let i = 0; i < urls.length; i += BATCH_SIZE) {
const batch = urls.slice(i, i + BATCH_SIZE);
const response = await fetch('https://api.fyralynx.com/v1/links/bulk', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.FYRALYNX_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
links: batch.map(url => ({ url, preset: 'auto' as const })),
}),
});
const { results: batchResults } = await response.json();
for (const item of batchResults) {
if (item.link !== undefined) {
results.push(item.link);
} else {
console.warn(`Item ${item.index} failed: ${item.error?.message ?? 'unknown'}`);
}
}
// Pause between batches to stay within rate limit (10 bulk req/min)
if (i + BATCH_SIZE < urls.length) {
await new Promise(resolve => setTimeout(resolve, 6100));
}
}
return results;
}POST /route - Get a routing decision
Runs the full EPC routing engine and returns the complete routing decision - including the winning candidate, all scored alternatives, and the CRC trace - without creating a persistent link and without counting a click.
Use this endpoint to:
- Preview routing outcomes before creating links at scale.
- Power your own routing UI with Fyralynx intelligence.
- Debug unexpected routing results without consuming monthly quota.
- Bulk-check merchant availability for a country.
Tier: Publisher+
Rate limit: 120 req/min (Publisher), 600 req/min (Enterprise) - higher than POST /links because this endpoint does not persist data.
Request body
json
{
"url": "https://www.amazon.com/dp/B0CRMZHDG4",
"preset": "auto",
"country": "NG",
"device": "android"
}| Field | Type | Required | Description |
|---|---|---|---|
url | string (URI) | ✅ | Merchant URL to evaluate. |
preset | string | Routing preset. Defaults to auto. | |
country | string (ISO 3166-1 alpha-2) | Visitor country for regional routing signals. | |
device | string | Visitor device type: desktop, ios, android. |
Response - 200 OK
json
{
"data": {
"url": "https://www.amazon.com/dp/B0CRMZHDG4",
"canonicalUrl": "https://www.amazon.com/dp/B0CRMZHDG4",
"winnerUrl": "https://www.jumia.com.ng/mlm-XXX.html?affid=…",
"preset": "auto",
"merchantId": "jumia",
"networkId": "InvolveAsia",
"region": "NG",
"confidence": 74,
"normalizedEpcUsd": 0.021,
"deepLinkUsed": false,
"crcTrace": {
"winner": {
"merchantId": "jumia",
"networkId": "InvolveAsia",
"region": "NG",
"routeScore": 0.0187,
"normalizedEpcUsd": 0.021,
"confidence": 74
},
"alternatives": [
{
"merchantId": "amazon",
"networkId": "CJ",
"region": "US",
"routeScore": 0.0024,
"normalizedEpcUsd": 0.039,
"confidence": 52,
"whyNotChosen": "Availability penalty applied: Amazon US does not ship to Nigeria (−0.20). Effective score dropped below Jumia NG."
}
],
"signals": [
{ "name": "availability", "value": -0.20, "label": "Availability penalty (Amazon US does not ship to NG)", "source": "essential" },
{ "name": "cod", "value": 0.75, "label": "COD factor (Nigeria - cash-on-delivery dominant)", "source": "essential" },
{ "name": "region_match", "value": 0.08, "label": "Region match (NG visitor → Jumia NG storefront)", "source": "essential" }
],
"scoringFormula": {
"intermediateScore": 0.0170,
"confidenceFactor": 1.10,
"finalScore": 0.0187
}
}
}
}Note: winnerUrl is the direct merchant URL with affiliate tracking parameters injected - not a go.fyralynx.com URL, because no persistent link was created. Use POST /links if you need a persistent routing URL.
curl example
bash
curl -X POST "https://api.fyralynx.com/v1/route" \
-H "Authorization: Bearer $FYRALYNX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.amazon.com/dp/B0CRMZHDG4",
"country": "NG",
"device": "android"
}'Passthrough Mode
When passthrough: true is set on a link (either at creation or via PATCH):
- No canonicalization is applied.
- No UTM parameters are injected or modified.
- No network routing decision is made.
- The edge redirector serves the original URL with a direct HTTP 302 - no processing.
- The click is still counted for billing purposes.
- Local analytics still records the click (merchant inferred from hostname where possible).
Passthrough mode is the correct choice for:
- Direct merchant program links (Flipkart, Noon, Jumia) where the creator's affiliate tag is already embedded by the merchant's dashboard.
- Brand-mandated exact tracking URLs where any modification would break attribution.
- Any link that must be delivered byte-for-byte as created.
bash
# Create a Flipkart direct-program link in passthrough mode
curl -X POST "https://api.fyralynx.com/v1/links" \
-H "Authorization: Bearer $FYRALYNX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.flipkart.com/sony-wh1000xm5/p/itmXXXXXXX?affid=myaffid123",
"passthrough": true,
"title": "Sony WH-1000XM5 (Flipkart)",
"preset": "auto"
}'Link Health Statuses
Each link has two independent health indicators:
healthStatus
| Value | Meaning |
|---|---|
ok | Redirect depth ≤ 1, no issues detected. Full EPC score applied. |
warning | Redirect depth 2-3, or minor UTM preservation issue. Small EPC penalty applied. |
critical | Redirect depth > 3, redirect loop detected, or 404 probability high. link_health_penalty_r of 0.15-0.25 applied. |
unknown | Link has not been probed yet. Probe is queued as a background task. |
deepLinkStatus
| Value | Meaning |
|---|---|
validated | HEAD probe succeeded within 3s. Deep-link is live. deeplink_bonus_r of up to +0.12 applied. |
unchecked | No probe has been run yet. Probe queued. No deep-link bonus applied. |
invalid | HEAD probe returned non-2xx/3xx status. No deep-link bonus. Confidence −0.05. |
ttl_expired | Validation result has expired (72h for valid, 24h for invalid). Re-probe queued. Confidence −0.05. |
Health checks are performed asynchronously as background tasks on Cloudflare Workers. The initial POST /links response may show healthStatus: unknown and deepLinkStatus: unchecked - these update within seconds.
Error Reference
| Code | Status | Description |
|---|---|---|
VALIDATION_ERROR | 422 | Field validation failed. Check errors array. |
UNSUPPORTED_MERCHANT | 422 | URL domain not in the Fyralynx merchant knowledgebase. Link created with passthrough: true. |
CLICK_QUOTA_EXCEEDED | 403 | Monthly click quota reached. Free tier: hard stop. Paid: upgrade or wait for reset. |
DUPLICATE_URL | 422 | URL already exists in your library (bulk create only). |
NOT_FOUND | 404 | Link with this ID does not exist or has been deleted. |
PUBLISHER_TIER_REQUIRED | 403 | API access requires Publisher or Enterprise plan. |
ENTERPRISE_TIER_REQUIRED | 403 | POST /links/bulk requires Enterprise plan. |
RATE_LIMIT_EXCEEDED | 429 | API rate limit exceeded. Honour Retry-After header. |
See Also
- API Overview - response format, pagination, error codes
- Authentication - API key setup
- Rate Limits - bulk patterns and retry strategies
- Merchants API - query merchant EPC data and availability
- Concepts: EPC Model - how
normalizedEpcUsdandconfidenceare computed - Concepts: Routing Explained - full routing engine walkthrough