grantus.

API & SDK

Spoj svoj produkt na grantus.

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.

Quickstart

Inštaluj SDK, nastav API kľúč v env, vypýtaj si zoznam aktívnych výziev.

TYPESCRIPT / JAVASCRIPT

@grantus/sdk

npm i @grantus/sdk

PYTHON 3.10+

grantus

pip install grantus

PHP 8.1+

grantus/grantus

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);
}

Autentifikácia

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.

Recepty

RECEPT · 01

Detail výzvy + história verzií

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

Hybridný vyhľadávač (keyword + semantic)

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

Vytvoriť watchdoga

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

Webhooky s HMAC podpisom

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

Iterovať cez všetky stránky

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);
}

Reference

Kompletný OpenAPI 3.1 spec je strojovo čitateľný /openapi.json. Pre Postman / Insomnia / Scalar stačí importovať túto URL.

METÓDACESTAPOPIS
GET/v1/callsList grant calls with filters + pagination
GET/v1/calls/:slugFull call detail
GET/v1/calls/:slug/versionsVersion history (deltas + diffs)
GET/v1/calls/searchHybrid keyword + semantic search
GET/v1/sourcesConfigured data sources
GET/v1/categoriesCategory vocabulary
GET/v1/target-groupsTarget groups vocabulary
GET/v1/regionsRegions vocabulary (NUTS3)
GET/v1/aid-typesAid types vocabulary
GET/v1/watchdogsList org watchdogs
POST/v1/watchdogsCreate watchdog
PATCH/v1/watchdogs/:idUpdate watchdog
DELETE/v1/watchdogs/:idDelete watchdog
GET/v1/notificationsRecent matches + delivery status
GET/v1/webhooksList webhooks
POST/v1/webhooksCreate webhook (returns secret)
PATCH/v1/webhooks/:idUpdate webhook
DELETE/v1/webhooks/:idSoft-delete webhook

Chyby

Typed exception hierarchy v SDK ti dovolí zachytiť celú rodinu jedným GrantusError a vetvit cez instanceof.

STATUSKÓDVÝZNAM
400validation_errorRequest body / query failed schema validation.
401unauthorizedMissing or invalid API key.
403forbiddenInsufficient plan or org role.
403feature_not_in_planEndpoint requires a higher plan tier.
403plan_limit_exceededHit a plan limit (seats / watchdogs / API).
404not_foundResource does not exist (or you cannot see it).
429rate_limit_exceededDaily quota exhausted; honour retry-after header.

PRIPRAVENÝ?

Tvoja prvá integrácia stojí 5 minút.

Získaj API kľúč v Settings, nainštaluj SDK a sleduj denný limit priamo v admine. Webhooky podpisujeme HMAC-SHA256 + Stripe-style timestamp tolerance.