Branded Short Links — iec-short
const IEC_SHORT_URL = process.env.IEC_SHORT_URL // injected — see Setup
Ruleconfigure
About
const IEC_SHORT_URL = process.env.IEC_SHORT_URL // injected — see Setup
Skill Content
This is the raw markdown that gets installed as a Claude Code rule.
# Branded Short Links — iec-short
## The Short Version
```typescript
const IEC_SHORT_URL = process.env.IEC_SHORT_URL // injected — see Setup
const res = await fetch(`${IEC_SHORT_URL}/api/v1/links`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${serviceToken}`, // your own service JWT — see Auth
},
body: JSON.stringify({
destinationUrl: 'https://policypay.com/portal/pay?loan=L-2024-67890&token=abc',
source: { service: 'my-service', channel: 'sms', templateName: 'payment-reminder' },
entity: { iecHash: '0x9f3a2b08721d4c5e8a1b3d7f6e0c2a49b5d81e37', type: 'insured', name: 'Cedar Creek Cattle Co' },
}),
})
const { shortUrl, slug } = await res.json()
// shortUrl === "https://insre.co/pay-0x9f3a2b08721d4c5e8a1b3d7f6e0c2a49b5d81e37"
```
`iec-short` is the platform's shortlink provider — branded `insre.co` URLs with
click analytics (device, browser, geo) and a hook back into Septor for chain
continuity ("message sent" → "link clicked"). It was built for `iec-relay` (SMS
character limits) but any service can use it for any outbound link.
## Setup
Declare it under `spec.dependencies`. The **Koko-registered name is `iec-short`**
(unlike `iec-relay` → `relay` or `iec-docman` → `docman`, this one keeps its
`iec-` prefix — verified via `tawa services list`):
```yaml
spec:
dependencies:
- service: iec-short # injects IEC_SHORT_URL (+ IEC_SHORT_CHARGE_MODE)
# transport omitted → defaults to janus (gas-metered, JWT-verified)
```
No `scopes:` needed — `iec-short` does not publish Bio-ID scopes (see Auth below),
so a bare dependency entry is all that's required.
## Auth — bring your own service token
`iec-short`'s routes are `auth: required`, but its `requireAuth` middleware does
**not** check scopes — it accepts *any* Bio-ID JWT that verifies against the JWKS
endpoint (a user token, or any service's own client-credentials token). That
means the calling service just needs its own Bio-ID credentials, which requires
declaring `spec.auth` (either mode gets you `BIO_CLIENT_ID`/`BIO_CLIENT_SECRET`):
```yaml
spec:
auth:
mode: service-only # or 'sso' if you also have user login
```
```typescript
import { BioAuth } from '@insureco/bio'
const bio = BioAuth.fromEnv()
const { access_token: serviceToken } = await bio.getClientCredentialsToken([])
await fetch(`${process.env.IEC_SHORT_URL}/api/v1/links`, {
headers: { Authorization: `Bearer ${serviceToken}` },
// ...
})
```
If your service already calls Bio-ID for something else, reuse that token —
there's nothing `iec-short`-specific about it.
## Creating Links
### Single link
```
POST /api/v1/links
```
| Field | Required | Notes |
|-------|----------|-------|
| `destinationUrl` | yes | the real URL to redirect to |
| `source.service` | yes | your service name — shows up in `GET /api/v1/links?service=` |
| `source.messageId` / `source.channel` / `source.templateName` | no | free-form context, useful for correlating back to `iec-relay` sends |
| `slug` | no | custom slug (3–60 chars, `[a-z0-9-]`); omit to auto-generate |
| `entity.iecHash` / `entity.type` / `entity.name` | no | InsureBio entity link. `iecHash` must be `0x` + 40 lowercase hex (iec-short rejects anything else with 400, e.g. a slug or a Mongo ObjectId); omit it if your record has none. When present with an inferred `action` (pay/quote/bind/cancel/renew/sign/doc), the slug auto-derives as `{action}-{iecHash}` instead of a random one |
| `septorChainId` | no | ties click events back into an existing Septor chain (e.g. the `messageId` of the relay send that generated this link) |
| `expiresAt` / `maxClicks` | no | link auto-deactivates (`410 Gone`) past either limit |
| `tags` | no | array of strings, filterable later |
```json
// 201 response
{
"id": "...",
"shortUrl": "https://insre.co/pay-0x9f3a2b08721d4c5e8a1b3d7f6e0c2a49b5d81e37",
"slug": "pay-0x9f3a2b08721d4c5e8a1b3d7f6e0c2a49b5d81e37",
"slugType": "auto",
"destinationUrl": "...",
"expiresAt": null,
"createdAt": "2026-09-14T..."
}
```
### Bulk create
```
POST /api/v1/links/bulk
```
Same shape, `{ "links": [ {...}, {...} ] }`, max **100** per request. Per-item
failures don't fail the batch — response is `{ created, failed, links, errors }`
with `errors[].index` pointing back into your input array.
## Querying & Managing Links
```
GET /api/v1/links?iecHash=&service=&messageId=&tag=&isActive=&limit=&offset=
GET /api/v1/links/:id
PATCH /api/v1/links/:id { destinationUrl?, isActive?, expiresAt?, maxClicks?, tags? }
```
`PATCH` invalidates the Redis redirect cache for that slug immediately — a
deactivated or redirected link takes effect on the very next click, not after a
TTL.
## Click Analytics
```
GET /api/v1/stats/:linkId # single link — device/browser/geo breakdown + daily timeline
GET /api/v1/stats?slug=|iecHash=|service=&days=30 # aggregate across a filter
```
`stats/:linkId` returns `devices`, `browsers`, `topLocations` (top 10 by click
count), and `clickTimeline` (daily counts) alongside `totalClicks` /
`uniqueClicks`. Uniqueness is tracked by a first-party `iec_short_cid` cookie on
the redirect hop, not by IP — a user without cookies enabled counts as unique on
every click.
## The Redirect Itself
`GET https://insre.co/:slug` — this is what actually fires when someone clicks
the link. No auth, 302 to `destinationUrl`. It's cache-first (Redis, with a
MongoDB fallback and re-cache on miss), so the extra hop is fast even under
load. Click tracking (`trackClick`) runs `setImmediate`-deferred and fire-and-
forget — a tracking failure never delays or breaks the redirect.
Failure responses on the redirect itself:
| Status | Cause |
|--------|-------|
| `404` | slug not found, or a reserved path (`api*`, `admin`, `favicon.ico`, `robots.txt`) |
| `410` | link deactivated, expired, or hit `maxClicks` |
## What NOT to Do
```typescript
// ❌ WRONG: hardcoding insre.co or a K8s DNS URL
await fetch('https://insre.co/api/v1/links', ...)
await fetch('http://iec-short.iec-short-prod.svc.cluster.local:3000/api/v1/links', ...)
// ✅ CORRECT: use the injected dependency URL
await fetch(`${process.env.IEC_SHORT_URL}/api/v1/links`, ...)
// ❌ WRONG: assuming a scope grant gates this — it doesn't
spec:
dependencies:
- service: iec-short
scopes: [short:create] # inert — iec-short publishes no scopes; harmless but does nothing
// ❌ WRONG: awaiting stats/creation on the hot path of an SMS/email send
await relay.sendEmail({ html: `<a href="${await shorten(url)}">Pay now</a>` })
// If iec-short is slow, your send is slow too — shorten opportunistically/async
// where the flow allows it, and fall back to the full URL on error.
```
## Key Facts
- **Koko name is `iec-short`, not `short`** — this one didn't get the prefix
stripped like `relay`/`docman` did. Double-check with `tawa services list` if
unsure before assuming a pattern.
- **No Bio-ID scopes published** — any verified Bio-ID token (yours or a user's)
is accepted. There's no per-caller allow-list today; don't rely on `iec-short`
itself to gate *who* can create links pointing anywhere.
- **No SDK package yet** — this doc is the only interface contract; calls are
plain `fetch`. If you build a wrapper, consider publishing `@insureco/short`
so this convention can be updated to reference it.
- Redirect responses are always `302`, never `301` — links are meant to be
re-pointable (`PATCH destinationUrl`) without browsers caching the redirect.
- `slug` auto-generation prefers `{action}-{iecHash}` (e.g.
`pay-0x9f3a2b08721d4c5e8a1b3d7f6e0c2a49b5d81e37`) over random, and falls back to an 8-char random slug
only on conflict or when no `entity.iecHash` is given.
- Uniqueness for `uniqueClickCount` is cookie-based (`iec_short_cid`), not
IP-based — treat it as directional, not exact.
- `databases: [mongodb, redis]` — MongoDB is the record of truth, Redis is a
redirect-speed cache invalidated on every `PATCH`.
Install
Copy the skill content and save it to:
~/.claude/rules/tawa-short-links.mdComing soon via CLI:
tawa chaac install tawa-short-linksDetails
- Format
- Rule
- Category
- configure
- Version
- 1.0.54640
- Tokens
- ~2,002
- Updated
- 2026-10-13
platformshort-linksinsre.corelayclick-trackingseptor