> ## Documentation Index
> Fetch the complete documentation index at: https://linkskipper.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Get an API key, resolve a link, and read back the destination in under five minutes.

This guide walks through the full loop: create a key, start a resolve, and read the final
destination. Every request is made over HTTPS to `https://api.linkskipper.app` and
authenticated with a bearer token.

<Steps>
  <Step title="Create an API key">
    Open the [developer dashboard](https://linkskipper.app/developers/keys) and generate a
    key. It looks like `sk_live_…` and carries the `resolve` scope. Copy it once and store
    it as a secret — the full value is shown only at creation time.

    <Warning>
      Treat the key like a password. It authorizes spending credits from your account. If a
      key leaks, revoke it in the dashboard and issue a new one.
    </Warning>
  </Step>

  <Step title="Make sure you have credits">
    Resolving a link spends credits (standard links cost **1**, premium links cost **2**).
    New accounts start with a small balance; top up in the
    [dashboard](https://linkskipper.app/developers/billing) when you need more. Cached links
    cost **0**.
  </Step>

  <Step title="Start a resolve">
    Send the shortener URL to `POST /v1/resolve`. If the link has been resolved before you
    get the destination back immediately (`200`, `status: "done"`). Otherwise it is queued
    for background resolution and you get a job to poll (`202`, `status: "queued"`).

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://api.linkskipper.app/v1/resolve \
        -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
        -H "Content-Type: application/json" \
        -d '{"url": "https://ouo.io/abc123"}'
      ```

      ```ts JavaScript theme={null}
      import { LinkSkipper } from "@linkskipper/sdk";

      const client = new LinkSkipper({ apiKey: process.env.LINKSKIPPER_API_KEY! });

      // resolveAndWait handles the queue + poll loop for you.
      const link = await client.resolveAndWait("https://ouo.io/abc123");
      console.log(link.targetUrl);
      ```

      ```php PHP theme={null}
      <?php

      use LinkSkipper\LinkSkipper;

      require __DIR__ . "/vendor/autoload.php";

      $client = LinkSkipper::create(getenv("LINKSKIPPER_API_KEY"));

      // resolveAndWait handles the queue + poll loop for you.
      $link = $client->resolveAndWait("https://ouo.io/abc123");
      echo $link->targetUrl, PHP_EOL;
      ```
    </CodeGroup>

    A queued response:

    ```json theme={null}
    {
      "job_id": "9b1d7c0e-2f3a-4b5c-8d6e-1a2b3c4d5e6f",
      "status": "queued",
      "queue_position": 3,
      "poll_url": "/v1/jobs/9b1d7c0e-2f3a-4b5c-8d6e-1a2b3c4d5e6f"
    }
    ```
  </Step>

  <Step title="Read the result">
    Poll `GET /v1/jobs/{job_id}` until `status` is `done` (or `failed` / `invalid`). The
    SDKs do this for you with `resolveAndWait`; with raw HTTP you poll on a short interval.

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://api.linkskipper.app/v1/jobs/9b1d7c0e-2f3a-4b5c-8d6e-1a2b3c4d5e6f \
        -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX"
      ```

      ```ts JavaScript theme={null}
      const job = await client.getJob("9b1d7c0e-2f3a-4b5c-8d6e-1a2b3c4d5e6f");
      if (job.status === "done") {
        console.log(job.targetUrl);
      }
      ```

      ```php PHP theme={null}
      $job = $client->getJob("9b1d7c0e-2f3a-4b5c-8d6e-1a2b3c4d5e6f");
      if ($job->status->value === "done") {
          echo $job->targetUrl, PHP_EOL;
      }
      ```
    </CodeGroup>

    ```json theme={null}
    {
      "job_id": "9b1d7c0e-2f3a-4b5c-8d6e-1a2b3c4d5e6f",
      "status": "done",
      "target_url": "https://example.com/final",
      "provider": "ouo",
      "tier": "standard",
      "credits_charged": 1,
      "balance": 248
    }
    ```
  </Step>
</Steps>

## Resolve without an SDK

If you prefer raw HTTP, the resolve-then-poll loop is a few lines in any language. These
examples retry on `429`/`5xx`-style transient failures by simply polling until the job
reaches a terminal status.

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Start a resolve job
  curl https://api.linkskipper.app/v1/resolve \
    -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
    -H "Content-Type: application/json" \
    -d '{"url": "https://ouo.io/abc123"}'
  # -> 202 { "job_id": "...", "status": "queued", "poll_url": "/v1/jobs/..." }

  # 2. Poll for the result (until status: done)
  curl https://api.linkskipper.app/v1/jobs/JOB_ID \
    -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX"
  # -> { "status": "done", "target_url": "https://..." }
  ```

  ```js JavaScript theme={null}
  const base = "https://api.linkskipper.app";
  const headers = { Authorization: "Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX" };

  const start = await fetch(base + "/v1/resolve", {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({ url: "https://ouo.io/abc123" }),
  });
  let job = await start.json();

  while (job.status === "queued" || job.status === "running") {
    await new Promise((r) => setTimeout(r, 1500));
    const poll = await fetch(base + "/v1/jobs/" + job.job_id, { headers });
    job = await poll.json();
  }

  console.log(job.target_url);
  ```

  ```php PHP theme={null}
  <?php

  $base = "https://api.linkskipper.app";
  $auth = "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX";

  $ch = curl_init($base . "/v1/resolve");
  curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_HTTPHEADER => [$auth, "Content-Type: application/json"],
      CURLOPT_POSTFIELDS => json_encode(["url" => "https://ouo.io/abc123"]),
  ]);
  $job = json_decode(curl_exec($ch), true);

  while (in_array($job["status"], ["queued", "running"], true)) {
      usleep(1_500_000);
      $poll = curl_init($base . "/v1/jobs/" . $job["job_id"]);
      curl_setopt_array($poll, [
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_HTTPHEADER => [$auth],
      ]);
      $job = json_decode(curl_exec($poll), true);
  }

  echo $job["target_url"], PHP_EOL;
  ```
</CodeGroup>

<Note>
  The official SDKs (`@linkskipper/sdk` and `linkskipper/sdk`) wrap this loop, typed errors,
  retries, and webhook verification. See [JavaScript](/docs/sdks/javascript) and
  [PHP](/docs/sdks/php).
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/docs/authentication">
    Keys, scopes, headers, and the webhook secret.
  </Card>

  <Card title="Resolve endpoint" icon="bolt" href="/docs/resolve">
    The full request and response contract.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/docs/webhooks">
    Get results pushed to you instead of polling.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/docs/errors">
    The RFC 7807 error envelope and every code.
  </Card>
</CardGroup>
