Skip to main content
POST https://api.linkskipper.app/v1/resolve · Requires the resolve scope.
Submit a supported shortener URL. If the link has already been resolved, the destination comes back immediately with HTTP 200. Otherwise the link is queued for background resolution and you get HTTP 202 with a job to poll (or a webhook delivery).

Request

Headers

Authorization
string
required
Bearer sk_live_…. See Authentication.
Content-Type
string
required
application/json.
Idempotency-Key
string
Optional. De-duplicates retries. Equivalent to the idempotency_key body field — if both are present, the body field wins. Max 128 characters. See Idempotency.

Body

url
string
required
The shortener URL to resolve. Max 2048 characters. Must be a link from a supported provider; anything else returns unsupported_link (422).
idempotency_key
string
Optional de-duplication token, max 128 characters. A repeated request with the same key returns the existing job’s current state instead of starting a new resolve. Takes precedence over the Idempotency-Key header.
webhook_url
string
Optional public HTTPS callback. When the job reaches a terminal status, Link Skipper sends a signed POST to this URL. Max 2048 characters. Loopback, private, link-local, and .localhost / .internal hosts are rejected at validation time. See Webhooks.
Request body
{
  "url": "https://ouo.io/abc123",
  "idempotency_key": "order-42",
  "webhook_url": "https://api.your-server.com/webhooks/linkskipper"
}

Response

The endpoint returns one of two shapes depending on whether the link was already cached.

200 — resolved (cached)

Returned when the link was already in cache. job_id is null because no job was created, cached is true, and credits_charged is 0 (cached reads are free).
job_id
string | null
null for a cached, synchronous resolve.
status
string
"done".
url
string
The URL you submitted (echoed back).
target_url
string
The final destination the shortener points to.
provider
string
The provider that owns the link (e.g. ouo). See Providers.
tier
string
"standard" or "premium".
credits_charged
number
0 for a cached resolve.
cached
boolean
true for a cached resolve.
balance
number | null
Your remaining credit balance after the call.
200 OK
{
  "job_id": null,
  "status": "done",
  "url": "https://ouo.io/abc123",
  "target_url": "https://example.com/final",
  "provider": "ouo",
  "tier": "standard",
  "credits_charged": 0,
  "cached": true,
  "balance": 248
}

202 — queued

Returned when the link must be resolved in the background. Poll the poll_url (the jobs endpoint) until the job is terminal, or supply a webhook_url to be notified.
job_id
string
The UUID of the queued job. Use it with GET /v1/jobs/{job_id}.
status
string
"queued".
queue_position
number
Approximate position in the resolve queue at submission time.
poll_url
string
Relative path to poll for the result, e.g. /v1/jobs/{job_id}.
202 Accepted
{
  "job_id": "9b1d7c0e-2f3a-4b5c-8d6e-1a2b3c4d5e6f",
  "status": "queued",
  "queue_position": 3,
  "poll_url": "/v1/jobs/9b1d7c0e-2f3a-4b5c-8d6e-1a2b3c4d5e6f"
}

Examples

curl https://api.linkskipper.app/v1/resolve \
  -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://ouo.io/abc123"}'
The SDK resolve() methods submit the URL and (optionally) an idempotency key. To pass a webhook_url, send a raw HTTP request, or use the dashboard. To resolve and wait for the result in one call, use resolveAndWait.

Synchronous vs queued

Cached → 200

The link was resolved before. The destination comes back in the same response, cached: true, and you are not charged.

New → 202

The link is queued. You get a job_id and poll_url. Credits are only charged when the job succeeds.
You don’t choose between the two — the API returns whichever applies. Always branch on the HTTP status (or the status field) so your code handles both.

Idempotency

A POST /v1/resolve is not naturally idempotent: a naive retry after a dropped connection could queue a second job and charge twice. Send an idempotency key to make retries safe. When you supply idempotency_key (body) or Idempotency-Key (header):
  • The first request with that key starts the resolve and remembers the resulting job.
  • Any repeat with the same key returns that job’s current state as-is — it does not start a new resolve and does not charge again. While the job is still running you get the 202 queued shape back; once it finishes you get the resolved 200 shape.
  • Idempotency keys are scoped per API key. Use a stable, unique value per logical operation (an order id, a content id, a UUID you generate).
Safe retry
# Re-running this exact request never opens a second job.
curl https://api.linkskipper.app/v1/resolve \
  -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-42" \
  -d '{"url": "https://ouo.io/abc123"}'
A replay of a key whose job ended in failure surfaces the same terminal error: resolve_failed for a failed job, unsupported_link for an invalid one.

Errors

CodeStatusWhen
invalid_request400Missing/unknown field, or url over 2048 chars.
invalid_key401Missing, malformed, or revoked API key.
out_of_credits402Balance is below the link’s cost. Includes balance.
forbidden_scope403Key lacks the resolve scope.
link_removed410The destination has been removed by the shortener.
unsupported_link422The URL is not a supported shortener (or has no URL).
rate_limited429Per-minute limit hit. Includes Retry-After.
quota_exceeded429Daily quota reached. Includes Retry-After.
resolve_failed502The resolver could not resolve the link.
provider_down503The provider is temporarily unavailable.
See Errors for the full envelope and handling guidance, and Rate limits for Retry-After behavior.