TYPESCRIPT / JAVASCRIPT
@grantus/sdk
npm i @grantus/sdk
API & SDK
REST API + oficiálne SDK pre TypeScript, Python a PHP. Klasifikované slovenské + EU výzvy, hybridný vyhľadávač, watchdog API, HMAC-podpísané webhooky.
Inštaluj SDK, nastav API kľúč v env, vypýtaj si zoznam aktívnych výziev.
TYPESCRIPT / JAVASCRIPT
npm i @grantus/sdk
PYTHON 3.10+
pip install grantus
PHP 8.1+
composer require grantus/grantus
import { GrantusClient } from '@grantus/sdk';
const grantus = new GrantusClient({ apiKey: process.env.GRANTUS_API_KEY! });
const { items } = await grantus.calls.list({
status: 'open',
perPage: 50,
});
for (const call of items) {
console.log(call.slug, call.title);
}Posielaj API kľúč v hlavičke X-Api-Key alebo ako Bearer token. Kľúče sa generujú v Settings → API kľúče. Plán Business+ je nutný.
Každý kľúč má denný limit (default 3000 volaní/deň). Server v každej odpovedi vracia hlavičky x-ratelimit-limit, x-ratelimit-remaining a x-ratelimit-reset.
RECEPT · 01
Watchdog deltas sú pre-computed — vidíš čo sa zmenilo a kedy bez vlastného diffu.
const call = await grantus.calls.get('mksr-fpu-2026-1');
const versions = await grantus.calls.listVersions(call.slug);
console.log(call.title, call.deadlineAt);
for (const v of versions) {
console.log(`v${v.versionNumber}`, v.diffSummary);
}RECEPT · 02
Meili pre kľúčové slová + pgvector pre sémantiku, zlúčené cez RRF (k=60). SK stop-words + synonymá sú v indexe.
const { hits } = await grantus.calls.search({
q: 'digitalizácia obcí',
status: ['open', 'closing-soon'],
perPage: 20,
});
for (const hit of hits) {
console.log(hit._rankingScore, hit.title);
}RECEPT · 03
Watchdog = uložené hľadanie + kanále. Match na nový/zmenený call prejde fanout-om a tvojimi kanálmi (email/webhook/Slack/Teams/Discord/Telegram/Push/RSS).
const watchdog = await grantus.watchdogs.create({
name: 'Digitalisation grants (SK)',
prompt: 'digitalizácia obcí',
channels: [
{ channel: 'email', config: {}, enabled: true },
{ channel: 'webhook', config: { url: 'https://example.com/hook' }, enabled: true },
],
});RECEPT · 04
Pošleme POST so Stripe-style podpisom (t=<unix>,v1=<hex>). Validuj na svojej strane: (1) porovnaj HMAC-SHA256(secret, t + '.' + body) s hodnotou v1 cez timing-safe compare, (2) odmietni ak |now − t| > 5 min (replay defense — bez tohto môže útočník replay-ovať platnú captured delivery navždy).
import { createHmac, timingSafeEqual } from 'node:crypto';
// Express/Fastify handler. Body MUST be the raw bytes (not parsed JSON)
// — many frameworks need an explicit raw-body middleware on this route.
app.post('/grantus', { config: { rawBody: true } }, (req, reply) => {
const header = req.headers['x-grantus-signature'] as string;
const match = /^t=(\d+),v1=([0-9a-f]+)$/.exec(header ?? '');
if (!match) return reply.code(400).send('bad signature header');
const [, tsStr, v1] = match;
// (1) Reject replays: payload is signed with current time; we accept
// ±5 minutes of clock skew. Without this check, a captured delivery
// can be replayed forever.
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - Number(tsStr)) > 300) {
return reply.code(408).send('signature timestamp outside window');
}
// (2) Constant-time HMAC compare. SECRET was shown ONCE on create —
// load from a secrets manager, never from disk in plaintext.
const expected = createHmac('sha256', process.env.GRANTUS_WEBHOOK_SECRET!)
.update(`${tsStr}.${req.rawBody}`)
.digest();
const provided = Buffer.from(v1!, 'hex');
if (expected.length !== provided.length || !timingSafeEqual(expected, provided)) {
return reply.code(401).send('signature mismatch');
}
const payload = JSON.parse(req.rawBody.toString('utf8'));
// process payload.event …
reply.code(204).send();
});RECEPT · 05
SDK majú asynchrónne iterátory (Generator v PHP), ktoré sťahujú stránky za teba — žiadny ručný page-counter.
for await (const call of grantus.calls.iterate({ status: 'open' })) {
await pipeline.process(call);
}Kompletný OpenAPI 3.1 spec je strojovo čitateľný /openapi.json. Pre Postman / Insomnia / Scalar stačí importovať túto URL.
| METÓDA | CESTA | POPIS |
|---|---|---|
| GET | /v1/calls | List grant calls with filters + pagination |
| GET | /v1/calls/:slug | Full call detail |
| GET | /v1/calls/:slug/versions | Version history (deltas + diffs) |
| GET | /v1/calls/search | Hybrid keyword + semantic search |
| GET | /v1/sources | Configured data sources |
| GET | /v1/categories | Category vocabulary |
| GET | /v1/target-groups | Target groups vocabulary |
| GET | /v1/regions | Regions vocabulary (NUTS3) |
| GET | /v1/aid-types | Aid types vocabulary |
| GET | /v1/watchdogs | List org watchdogs |
| POST | /v1/watchdogs | Create watchdog |
| PATCH | /v1/watchdogs/:id | Update watchdog |
| DELETE | /v1/watchdogs/:id | Delete watchdog |
| GET | /v1/notifications | Recent matches + delivery status |
| GET | /v1/webhooks | List webhooks |
| POST | /v1/webhooks | Create webhook (returns secret) |
| PATCH | /v1/webhooks/:id | Update webhook |
| DELETE | /v1/webhooks/:id | Soft-delete webhook |
Typed exception hierarchy v SDK ti dovolí zachytiť celú rodinu jedným GrantusError a vetvit cez instanceof.
| STATUS | KÓD | VÝZNAM |
|---|---|---|
| 400 | validation_error | Request body / query failed schema validation. |
| 401 | unauthorized | Missing or invalid API key. |
| 403 | forbidden | Insufficient plan or org role. |
| 403 | feature_not_in_plan | Endpoint requires a higher plan tier. |
| 403 | plan_limit_exceeded | Hit a plan limit (seats / watchdogs / API). |
| 404 | not_found | Resource does not exist (or you cannot see it). |
| 429 | rate_limit_exceeded | Daily quota exhausted; honour retry-after header. |
PRIPRAVENÝ?
Získaj API kľúč v Settings, nainštaluj SDK a sleduj denný limit priamo v admine. Webhooky podpisujeme HMAC-SHA256 + Stripe-style timestamp tolerance.