npm package: @hopla/sickw
Description: TypeScript client for the Sickw IMEI check/unlock API
Author: Julio Moreno
Latest version: 1.3.3

# @hopla/sickw

TypeScript client for the [Sickw](https://sickw.com) IMEI check/unlock API.

- Zero dependencies (uses native `fetch`)
- ESM + CJS + full type definitions
- React hooks via `@hopla/sickw/react` (no external dependencies)
- Claude Code skill for AI-assisted development
- Tree-shakeable (`sideEffects: false`)
- Node.js >= 18

## Install

```bash
npm install @hopla/sickw
```

### Claude Code Skill (optional)

Install the skill so Claude Code knows how to use this library when building your projects:

```bash
# Global — available in all projects
npx sickw-skill-install --global

# Project-level — only for current repo
npx sickw-skill-install
```

Once installed, Claude will automatically use `@hopla/sickw` when you ask it to build features involving IMEI checks, carrier lookups, iCloud status, phone unlocks, etc. You can also invoke it explicitly with `/sickw`.

To uninstall: `npx sickw-skill-uninstall --global`

## Quick Start (React + Cloudflare Worker)

### 1. Backend proxy (Cloudflare Worker with Hono)

**Never expose your API key in the browser.** Create a Worker route that forwards requests to Sickw:

```typescript
// worker/src/routes/sickw.ts
import { Hono } from 'hono';
import { createSickwClient } from '@hopla/sickw';

const app = new Hono<{ Bindings: { SICKW_API_KEY: string } }>();

app.post('/sickw/check', async (c) => {
  const sickw = createSickwClient({ apiKey: c.env.SICKW_API_KEY });
  const { imei, serviceId } = await c.req.json();
  const result = await sickw.check(imei, serviceId);
  return c.json({ success: true, data: result });
});

app.get('/sickw/services', async (c) => {
  const sickw = createSickwClient({ apiKey: c.env.SICKW_API_KEY });
  const services = await sickw.services();
  return c.json({ success: true, data: services });
});

app.get('/sickw/balance', async (c) => {
  const sickw = createSickwClient({ apiKey: c.env.SICKW_API_KEY });
  const balance = await sickw.balance();
  return c.json({ success: true, data: balance });
});

export default app;
```

### 2. Use the hooks (frontend)

The hooks receive a generic `fetchFn` — use your app's existing fetch wrapper (e.g., `apiPost`/`apiGet`):

```tsx
import { useSickwCheck, useSickwServices, useSickwBalance } from '@hopla/sickw/react';
import { isValidImeiOrSn } from '@hopla/sickw';
import { apiPost, apiGet } from '@/lib/api'; // your fetch wrapper
import { useState } from 'react';

function ImeiChecker() {
  const [imei, setImei] = useState('');
  const [serviceId, setServiceId] = useState(3);

  const { data: services } = useSickwServices(
    () => apiGet('/sickw/services').then(r => r.data!)
  );
  const { data: balance } = useSickwBalance(
    () => apiGet('/sickw/balance').then(r => r.data!),
    60_000 // refetch every 60s
  );
  const check = useSickwCheck(
    (params) => apiPost('/sickw/check', params).then(r => r.data!)
  );

  const isValid = imei.length === 0 || isValidImeiOrSn(imei);

  return (
    <div>
      <p>Balance: ${balance}</p>

      <input
        value={imei}
        onChange={(e) => setImei(e.target.value.trim())}
        placeholder="Enter IMEI or Serial Number"
        maxLength={15}
        style={{ borderColor: isValid ? undefined : 'red' }}
      />

      <select value={serviceId} onChange={(e) => setServiceId(Number(e.target.value))}>
        {services?.map((s) => (
          <option key={s.service} value={s.service}>
            {s.name} (${s.price})
          </option>
        ))}
      </select>

      <button
        disabled={!isValidImeiOrSn(imei) || check.isPending}
        onClick={() => check.mutate({ imei, serviceId })}
      >
        {check.isPending ? 'Checking...' : 'Check IMEI'}
      </button>

      {check.isSuccess && (
        <pre>{JSON.stringify(check.data!.result, null, 2)}</pre>
      )}

      {check.isError && (
        <p style={{ color: 'red' }}>{check.error!.message}</p>
      )}
    </div>
  );
}
```

## React Hooks

| Hook | Type | Signature |
|------|------|-----------|
| `useSickwCheck(fetchFn, options?)` | Action (manual trigger) | `→ { data, error, isPending, isSuccess, isError, mutate, reset }` |
| `useSickwHistory(fetchFn, options?)` | Action (manual trigger) | `→ { data, error, isPending, isSuccess, isError, mutate, reset }` |
| `useSickwServices(fetchFn)` | Auto-fetch on mount | `→ { data, error, isLoading }` |
| `useSickwBalance(fetchFn, intervalMs?)` | Auto-fetch + refetch | `→ { data, error, isLoading, refetch }` |

**No `@tanstack/react-query` or any other external dependency required.** The hooks use vanilla `useState`/`useEffect`/`useCallback`.

Each hook receives a `fetchFn` — a function that calls your backend. This lets you plug in any fetch wrapper (`apiPost`, `apiGet`, plain `fetch`, etc.) without coupling to the Sickw client directly.

## Node.js / Backend Usage

The client also works standalone without React:

```typescript
import { createSickwClient } from '@hopla/sickw';

const client = createSickwClient({
  apiKey: 'ABC-DEF-GHI-JKL-MNO-PQR-STU-VWX',
});

const result = await client.check('354442067957123', 3);
const balance = await client.balance();
const services = await client.services();
const history = await client.history('76369088');
```

## Validators

Validate user input before submitting:

```typescript
import { isValidImeiOrSn, isValidServiceId, isValidApiKey } from '@hopla/sickw';

isValidImeiOrSn('354442067957123'); // true — 11-15 alphanumeric chars
isValidServiceId(3);                // true — integer 0-250
isValidApiKey('ABC-DEF-GHI-...');   // true — XXX-XXX-XXX-XXX-XXX-XXX-XXX-XXX
```

## Service Catalog

All 84 services are available as a typed constant for building UI dropdowns, filtering, etc.:

```typescript
import { SERVICES, getServiceById, getServicesByCategory } from '@hopla/sickw';

// Lookup by ID
const icloud = getServiceById(3);
// { id: 3, name: 'iCloud On/Off', price: '0.01', category: 'Apple', instant: true }

// Filter by category
const appleServices = getServicesByCategory('Apple'); // 21 services

// Group for a dropdown
const categories = ['Apple', 'Status', 'Generico', 'Premium', 'Unlock iPhone', 'Unlock Gen'];
```

| Category | Count | Instant |
|----------|-------|---------|
| Apple | 21 | Yes |
| Status | 10 | Mostly |
| Generico | 23 | Yes |
| Premium | 1 | No |
| Unlock iPhone | 22 | No |
| Unlock Gen | 7 | No |

## Error Handling

API errors are thrown as `SickwError` with a typed error code:

```typescript
import { SickwError } from '@hopla/sickw';

try {
  const result = await sickw.check(imei, serviceId);
} catch (err) {
  if (err instanceof SickwError) {
    console.log(err.code);       // 'E01', 'B01', 'A01', etc.
    console.log(err.rawMessage); // Original API error string
  }
}
```

| Code | Description |
|------|-------------|
| E01 | IMEI failed Luhn checksum |
| E02 | IMEI/SN invalid format or length |
| R01 | Service offline or IMEI/SN not found |
| B01 | Insufficient balance |
| S01/S02 | Service ID does not exist or is inactive |
| S03 | Service is not instant |
| A01/A02/A03 | Invalid API key |

## Configuration

```typescript
createSickwClient({
  apiKey: 'XXX-XXX-...',     // Required (can be empty for proxy usage)
  baseUrl: 'https://...',    // Default: 'https://sickw.com'
  timeout: 60000,            // Default: 60000ms
});
```

## Documentation

Full API documentation is included in the [`docs/`](./docs) directory:

- [`docs/Sickw API Reference.md`](./docs/Sickw%20API%20Reference.md) — All 84 service IDs with prices and categories
- [`docs/api-docs/Sickw API.md`](./docs/api-docs/Sickw%20API.md) — Endpoint, auth, formats, error codes
- [`docs/api-docs/Servicios Apple.md`](./docs/api-docs/Servicios%20Apple.md) — 21 Apple services (iCloud, carrier, MDM, GSX)
- [`docs/api-docs/Servicios Status Check.md`](./docs/api-docs/Servicios%20Status%20Check.md) — 10 carrier/blacklist status services
- [`docs/api-docs/Servicios Genericos.md`](./docs/api-docs/Servicios%20Genericos.md) — 23 brand info services
- [`docs/api-docs/Servicios Premium.md`](./docs/api-docs/Servicios%20Premium.md) — MDM bypass
- [`docs/api-docs/Servicios Unlock iPhone.md`](./docs/api-docs/Servicios%20Unlock%20iPhone.md) — 22 iPhone unlock services
- [`docs/api-docs/Servicios Unlock Generico.md`](./docs/api-docs/Servicios%20Unlock%20Generico.md) — 7 generic unlock services
- [`docs/guides/typescript-api-client.md`](./docs/guides/typescript-api-client.md) — Guide: building a TS client
- [`docs/guides/react-integration.md`](./docs/guides/react-integration.md) — Guide: React hooks and proxy pattern

## License

MIT