npm package: @snap-pink/sdk
Description: Full-platform SDK for the snap.pink QR API.
Latest version: 0.9.0

# `@snap-pink/sdk`

Full-platform SDK for the snap.pink QR code and URL shortener API.

Default API base URL: `https://api.snap.pink`

## Install

```bash
npm install @snap-pink/sdk
```

## Quick start

```js
import { createSnapPinkClient } from "@snap-pink/sdk";

const client = createSnapPinkClient({
  apiKey: process.env.SNAP_PINK_API_KEY,
});

// Shorten a URL
const link = await client.createLink({
  url: "https://example.com/long-page",
  customSlug: "my-link",
});
console.log(link.shortUrl); // https://go.snap.pink/my-link

// Create a dynamic QR code
const qrCode = await client.createQrCode({
  name: "Spring Campaign",
  kind: "dynamic",
  currentDestinationUrl: "https://brand.com/spring",
  payload: { type: "url", url: "https://brand.com/spring" },
  analyticsEnabled: true,
  customSlug: "spring-2026",
  expiresAt: "2026-06-01T00:00:00Z",
  utmParams: { source: "qr", medium: "print", campaign: "spring" },
});
```

## Free static QR (no auth)

```js
import { createStaticQr, createStaticQrSvg } from "@snap-pink/sdk";

const qr = await createStaticQr({ url: "https://snap.pink" });
const svg = await createStaticQrSvg({ payloadType: "text", text: "Hello" });
```

## Free URL shortener (no auth)

```js
import { shortenUrl } from "@snap-pink/sdk";

const result = await shortenUrl("https://example.com/very/long/page");
console.log(result.shortUrl); // https://go.snap.pink/a3f7b2c1

// Or with an object:
const result2 = await shortenUrl({ url: "https://example.com/page" });
```

Free short links are permanent, uneditable, and have no analytics.
Every URL is screened against Google Web Risk and a domain blocklist before a short link is created.
For editable links with tracking, use `client.createLink()` (requires auth).

## Error handling

All API errors throw a `SnapPinkError` with `code` and `status` fields:

```js
import { shortenUrl, SnapPinkError } from "@snap-pink/sdk";

try {
  await shortenUrl("https://suspicious-site.example");
} catch (err) {
  if (err instanceof SnapPinkError) {
    console.error(err.code);   // "blocked_domain", "safe_browsing:MALWARE", etc.
    console.error(err.status); // 422
    console.error(err.message); // "This URL is not allowed."
  }
}
```

URL safety error codes (apply to **all** endpoints that accept a destination URL — free shortener, paid links, and QR codes):

| Code | Meaning |
|------|---------|
| `invalid_protocol` | URL must start with http:// or https:// |
| `url_too_long` | URL exceeds 2048 characters |
| `invalid_url` | Malformed URL |
| `self_referential` | Cannot shorten snap.pink URLs |
| `blocked_domain` | Domain is on the blocklist (adult, spam, shortener chains) |
| `blocked_content` | URL path contains blocked keywords |
| `safe_browsing:*` | Flagged by Google Web Risk (malware, phishing, etc.) |
| `rate_limited` | Too many requests (5/hour, 15/day per IP) |

## Report abusive links (no auth)

```js
import { reportLink } from "@snap-pink/sdk";

await reportLink("a3f7b2c1", "Phishing attempt");
```

Reports are reviewed and actioned by the platform.

## API reference

### Links (URL shortener)

```js
const link = await client.createLink({ url, customSlug?, expiresAt?, password?, tags? });
const { data, hasMore } = await client.listLinks({ limit?, cursor?, status?, search? });
await client.updateLink({ linkId, url?, customSlug?, expiresAt?, password? });
await client.deleteLink(linkId);
```

### QR codes

```js
await client.createQrCode({ name, kind, payload, design?, customSlug?, ... });
await client.getQrCode(qrCodeId);
const { data, hasMore } = await client.listQrCodes({ limit?, cursor?, kind?, tag? });
await client.updateQrCode({ qrCodeId, currentDestinationUrl?, status?, ... });
await client.deleteQrCode(qrCodeId);           // soft delete (archive)
await client.permanentlyDeleteQrCode(qrCodeId); // permanent delete
await client.duplicateQrCode(qrCodeId, name?);
await client.batchQrCodes({ operation, qrCodeIds, ... });
```

### Routing rules

```js
await client.listRoutingRules(qrCodeId);
await client.createRoutingRule({ qrCodeId, condition, destinationUrl, priority? });
await client.updateRoutingRule({ ruleId, qrCodeId, condition?, destinationUrl? });
await client.deleteRoutingRule(ruleId, qrCodeId);
await client.reorderRoutingRules(qrCodeId, ruleIds);
```

Condition types: `geo_country`, `geo_region`, `device`, `os`, `language`, `ab_split`, `time_window`, `date_range`.

### Retargeting pixels

```js
await client.listPixels();
await client.createPixel({ name, platform, pixelId });
await client.updatePixel({ pixelId, name?, isEnabled? });
await client.deletePixel(pixelId);
```

Platforms: `meta_pixel`, `google_ads`, `tiktok`, `linkedin`, `twitter`, `custom`.

### Webhooks

```js
await client.listWebhooks();
await client.createWebhook({ url, secret, events });
await client.updateWebhook({ webhookId, url?, events?, isEnabled? });
await client.deleteWebhook(webhookId);
await client.testWebhook(webhookId);
```

Events: `scan`, `link.click`, `link.created`, `link.updated`.

### UTM templates

```js
await client.listUtmTemplates();
await client.createUtmTemplate({ name, source?, medium?, campaign?, term?, content? });
await client.updateUtmTemplate({ templateId, name?, source?, ... });
await client.deleteUtmTemplate(templateId);
```

### Bio pages (link-in-bio)

```js
await client.listBioPages();
await client.createBioPage({ slug, title, links, theme?, socialLinks? });
await client.updateBioPage({ bioPageId, title?, links?, isPublished? });
await client.deleteBioPage(bioPageId);
await client.addBioPageQr(bioPageId);  // retroactively create a linked QR code
```

### Discovery

```js
await client.publishToDiscovery({ qrCodeId, enabled, title?, category?, tags?, publisherDisplayName? });
await client.getDiscoveryListing(qrCodeId);
```

### Folders

```js
await client.listFolders();
await client.createFolder({ name, description?, color? });
await client.updateFolder({ folderId, name?, description? });
await client.deleteFolder(folderId);        // soft delete (archive)
await client.archiveFolder(folderId);       // same as deleteFolder
await client.destroyFolder(folderId);       // permanent delete (must be archived first)
```

### Domains

```js
await client.listDomains();
await client.createDomain({ hostname });
await client.updateDomain({ domainId, isPrimary? });
await client.deleteDomain(domainId);
await client.verifyDomain(domainId);
```

### Analytics

```js
const analytics = await client.getAnalytics({ qrCodeId?, startDate?, endDate?, granularity? });
const csv = await client.exportAnalyticsCsv({ startDate?, endDate? });
```

### Bulk jobs

```js
const job = await client.createQrCodesBulk(rows);
const status = await client.getBulkJob(jobId);
```

### Billing

```js
const wallet = await client.getWallet();
await client.updateAutoTopOff({ autoTopOffEnabled, autoTopOffAmountMicrosUsd });
await client.initiateTopOff();
```

## Pricing

Pay-per-use, no monthly fees:

| Action | Cost |
|--------|------|
| Create short link / dynamic QR | $0.05 |
| Click / scan | $0.0005 |
| Static QR | Free |
| Routing rules, pixels, webhooks | Included in click cost |

## License

MIT