Open source

No lock-in. By construction.

Kirak is an open-source backend runtime. Declare your data model, auth, and access rules in JSON; Kirak serves the whole REST API — CRUD, authentication, row-level security, payments, storage, notifications, AI agents, and MCP servers — live from that declaration. Self-host it anywhere. No account, no key, no phone-home.

$pip install kirak
Apache 2.0
OSI-approved — fork, run, resell
Python 3.10+
Built on FastAPI
MySQL · Postgres
Switch with one config line
Why it's open

Built once, for every project we shipped.

⚑ Placeholder story — to be rewritten by the Kirak team

Kirak started inside a small team that built software for other people. Every new project began the same way: wire up authentication, write the CRUD endpoints, bolt on row-level checks, integrate a payment provider, add file uploads, set up a job scheduler. Different product, same backend plumbing — rebuilt, retested, and re-secured from scratch every time.

So in 2024 we stopped rebuilding it. We took the parts that never really changed — the CRUD machinery, the auth flows, the access rules, the integrations — and moved them into a single runtime that reads a declaration and serves all of it generically. Internally it was called Crux. The only code we wrote per project after that was the logic genuinely unique to the app: a handful of lifecycle hooks and custom endpoints.

We ran it in production across roughly a dozen real projects over about a year before deciding it shouldn't be ours alone. Every vibe-coded app hits the same wall — insecure, bloated, expensive-to-fix backends — rebuilding exactly what we'd already solved. Kirak is that runtime, released as genuine open source: no restriction on how you use it, including running it as a competing hosted service.

The company runs a managed service, Kirak Studio, on top of the same runtime — that's how the project is funded. Studio's hosting, build agent, and operating console are separate proprietary code; none of it is in this repository, and the runtime never depends on it.

Quickstart

Running in about five minutes.

01

Install the runtime

Python 3.10 or newer.

terminal
pip install kirak
02

Scaffold a project

Creates main.py, kirak.json, a sample model in models/, hooks/, an AGENTS.md for coding agents, and JSON Schemas your editor picks up. The project's pyproject.toml pulls in the database driver.

terminal
kirak new my-backend --database postgres   # or mysql
cd my-backend
pip install -e .
cp .env.example .env   # secrets only: DB password + JWT keys
03

Declare a model

One file per model: its fields and access rules in models/note.json. Run kirak validate to check it.

models/note.json
{
  "schema": {
    "title": {"type":"string","required":true},
    "body": {"type":"text"},
    "owner_id": {"type":"integer","required":true}
  },
  "access": {
    "fetch":  [{"role":"user","condition":"owner_id = {user_id}"}],
    "create": [{"role":"user","condition":"owner_id = {user_id}"}]
  }
}
04

Run it

Applies the schema to your database, then starts serving on :8000.

terminal
kirak db init
uvicorn main:app --reload
05

Call the API

Full CRUD on /note, access-filtered to the authenticated user by owner_id.

terminal
curl -X POST localhost:8000/note/create \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"data":{"title":"first","body":"hello"}}'
In the runtime

Everything a production backend needs. In the open-source release.

Every capability below ships in the open-source release. The runtime serves all of it from your declaration — blue is served automatically, amber is the one layer where you write real, owned code.

Served by the runtime

Model-driven REST API

Every model file in models/ becomes a documented endpoint set — list, search, count, create, update, upsert, soft delete and restore, hard delete — with filtering, pagination, sorting, per-model rate limits, and before/after hooks on every operation. The same operations are served over a shared GraphQL endpoint, under the same access rules. Change a field and the API changes with it.

FastAPI · Pydantic · OpenAPI 3.1 and a Markdown reference for AI agents, generated and kept in sync
$ uvicorn main:app  ·  served from models/booking.json
/booking10 endpoints
GET/booking/fetchlist + filter + paginate
GET/booking/searchfull-text search
GET/booking/countcount matching rows
GET/booking/existsdoes a match exist?
POST/booking/createvalidate + create
PUT/booking/updateupdate, filtered by "where"
PUT/booking/upsertinsert or update
DELETE/booking/deletesoft delete, with "soft_delete": true
PATCH/booking/restoreundo a soft delete
DELETE/booking/destroyhard delete
/graphql1 endpoint
POST/graphqlqueries, mutations, aggregates · same access rules
/docs · /openapi.json2 endpoints
GET/docsMarkdown API reference, written for AI agents
GET/openapi.jsonOpenAPI 3.1, always in sync
Served by the runtime

Row- & field-level security

Per-role access rules declared as an access block in each model file, enforced at the query level on every list, read, and join — for every role. Field rules go further: hide a column from some roles, or make it read-only. Declarative, not a bolt-on policy language you maintain separately.

Enforced identically on REST, GraphQL, MCP tools, agent tools, and your hooks
models/booking.json
"access": {
  "fetch":  [
    {"role":"admin"},
    {"role":"user", "condition":"renter_id = {user_id}"}
  ],
  "create": [{"role":"user"}],
  "update": [{"role":"user", "condition":"renter_id = {user_id}"}],
  "destroy": [{"role":"admin"}],
  "fields": {
    "internal_notes": { "read": ["admin"], "write": ["admin"] }
  }
}
Served by the runtime

Authentication

MFA, passwordless SMS one-time codes, API keys, and email verification are built directly into Kirak Auth — Kirak's own build on JWT sessions with refresh-token rotation, not bolted on from a third-party library. Social / OAuth login is powered separately by Social Core, covering 200+ provider backends.

GoogleGitHubAppleMicrosoftLinkedInDiscord+ 200 more
MFA, OTP, and API keys are Kirak's own build · passkeys & SSO are on the roadmap
POST/auth/registeremail + password
POST/auth/login+ mfa_code in the same call
POST/auth/logoutblacklist the access token
GET/auth/mecurrent authenticated user
POST/auth/refresh-tokenrotates access + refresh
POST/auth/request-reset-password+ /reset-password
POST/auth/request-otp+ /verify-otp · passwordless SMS code
POST/auth/api-keys/createserver-to-server key · X-API-Key
GET/auth/{provider}/loginsocial login redirect
Served by the runtime

Payments

Checkout, one-off charges, subscriptions (cancel, change, pause, resume), refunds, saved cards and off-session charges, disputes, and Stripe Connect marketplace payouts — with webhook verification and reconciliation wired up, served by the runtime, not a hook you maintain. Run several gateways side by side as named providers; amounts are handled in each currency's minor unit. Provisioning logic lives in an after_webhook hook when you need it.

StripePayPalPaddleRazorpaySquarePaystackFlutterwaveMercado PagoXenditAirwallexOmiseTelr
Webhook signature checks, replay protection, dedup, and idempotency keys handled by the runtime
POST/payments/initiatecheckout, or a subscription
POST/payments/webhook/{service}provider callback · signature-verified
POST/payments/connect/onboardStripe Connect merchant onboarding
POST/payments/connect/checkoutmarketplace checkout + platform fee
POST/payments/methods/setupsave a card for later
GET/payments/methodsthe caller's saved methods
GET/payments/providersconfigured gateways + what each supports
Served by the runtime

Storage

File and image upload with automatic image compression and thumbnail generation. Declare an upload field and the runtime handles multipart parsing, validation, and the storage backend — local disk or any of nine object stores, several at once as named providers.

Local diskAmazon S3Cloudflare R2Google Cloud StorageAzure BlobDigitalOcean SpacesBackblaze B2WasabiOVHcloudCubbit
Backend switched in kirak.json — no code change
POST/storage/upload/imagemultipart, with compression + thumbnails
POST/storage/upload/filemultipart, non-image
GET/storage/urlpublic or presigned URL
DELETE/storage/deleteremove object, optionally + thumbnails
Served by the runtime

Notifications

Email, SMS, push, instant messages to Slack, Discord, or Telegram, signed outgoing webhooks, and an in-app notification inbox, each with its own send method — delivery, templates, and per-user preference checks are runtime concerns; you call kirak.notifications from a hook or route.

SESSendGridSMTPSNSTwilioFirebaseAPNsHuawei PushSlackDiscordTelegramWebhooks
Provider chosen per channel in kirak.json
GET/notifications/inboxin-app inbox · paginated
PUT/notifications/inbox/{id}/readmark read
POST/notifications/send-multiseveral channels in one call
POST/notifications/send-imSlack · Discord · Telegram
POST/notifications/send-webhooksigned outgoing webhook
GET/notifications/preferencesper-user channel opt-in
Served by the runtime

Scheduler

Background job queue and cron scheduler with zero extra infrastructure by default — DB-backed out of the box. Register a job with a decorator, or create schedules at runtime over HTTP; the runtime handles timing, retries with backoff, timeouts, crash recovery, and history. Route queues to several backends at once.

DB-backedRedisRabbitMQ
DB-backed default · Redis and RabbitMQ backends for scale, side by side
POST/scheduler/enqueueenqueue a job over HTTP
GET/scheduler/jobsrecent jobs + status
GET/scheduler/registeredregistered tasks + cron expressions
DELETE/scheduler/jobs/{job_id}cancel a pending job
POST/scheduler/schedulescreate a cron schedule at runtime
POST/scheduler/schedules/{id}/runrun a schedule now
Served by the runtime

Caching

Native Redis query caching: switch it on for every model in kirak.json, or per model in its own file. The runtime keys each entry by what the caller is allowed to see, invalidates on write, and serves reads from cache — no cache code in your project.

Redis
Redis — invalidation handled on every create / update / delete
// default for every model
"cache": {
  "enabled": true,
  "default_ttl_seconds": 60
}
Served by the runtime

AI agents

Declare agents in agents/*.json: a model, instructions, and the Kirak operations — or whole MCP servers — they may use as tools. Every tool call runs as the caller, under your access rules. Mark a tool requires_confirmation and the run pauses for a human to approve it. Cost caps, conversations, streaming, structured output, and a full run history are built in. Built on pydantic-ai — so any model pydantic-ai supports works, not a fixed provider list: OpenAI, Anthropic, Gemini, OpenRouter, and more. Bring your own key. Distinct from Studio's own agent that builds the backend; these are agents your app runs.

OpenAIAnthropicGeminiOpenRouter+ any pydantic-ai model
BYOK · called as kirak.ai from a hook, or over the auto-mounted HTTP routes · each agent's access decides who may run it
POST/ai/promptone-shot generation, optional structured output
GET/ai/agentsdeclared agents
POST/ai/agents/{name}/runrun an agent · streaming
POST/ai/agents/{name}/resumecontinue after human approval
GET/ai/agent/runsrun history, with tool calls + cost
Served by the runtime

Vector & RAG

Vector stores and embeddings for retrieval-augmented generation: create indexes, upsert text or vectors, and search with metadata filters. Agents use it as a tool, so the agent decides when to retrieve — there's no fixed pipeline to maintain. No HTTP endpoints by design: you call it from your own routes, hooks, and agents, where your access rules apply.

PineconeS3 VectorsEmbeddings:OpenAIGeminiOllama
Stores and embedding providers configured independently in kirak.json
kirak.vector — in any hook, route, or agent tool
await kirak.vector.upsert({
    "index": "docs",
    "vectors": [{"id": "faq-1", "text": "Refunds take 5 days."}],
})

result = await kirak.vector.search({
    "index": "docs",
    "text": "How long do refunds take?",
    "top_k": 5,
})
Served by the runtime

MCP servers

Declare an MCP server per audience — an admin server for an internal agent, a support server for a customer-facing one, one for a UI builder — each a JSON file in mcp/ listing the operations it offers as tools, who may connect, and a rate limit. Any MCP client can connect; every tool call runs as the caller, under your access rules. Nothing is exposed until you declare it. Works with Lovable and Bolt today via manual MCP configuration — not yet a built-in default inside those tools.

Streamable HTTP at /mcp/<name>/ · bearer token or API key · tool calls recorded by monitoring
mcp/support.json  ·  served at /mcp/support/
{
  "name": "support",
  "description": "Order lookups for the support team",
  "access": "User",
  "rate_limit_per_minute": 60,
  "tools": {
    "orders.fetch": {},
    "orders.search": {},
    "orders.update": { "timeout": 10 }
  }
}
Built for coding agents

An agent-ready project

A Kirak project is mostly JSON, so a coding agent needs facts it can't guess and a way to know its edit is valid. kirak new writes an AGENTS.md and JSON Schemas for every file; kirak validate checks each edit before the app starts; kirak catalog --json and friends report modules, providers, settings, and secrets for the installed version; kirak dev mcp serves all of it to the agent over MCP; and GET /docs describes the running API in Markdown.

Claude CodeCursorCodex
The rule for an agent: ask the CLI instead of guessing, and validate after every edit — read the guide
terminal
kirak validate                   # models, kirak.json, agents, MCP servers
kirak catalog --json             # every module, provider, and setting
kirak providers payments --json  # settings + secrets for each gateway
kirak env                        # the secrets this project needs
kirak dev mcp                    # all of the above, over MCP
Served by the runtime

Monitoring

Application performance data, structured logs, error tracking, per-request tracing, audit records, AI run costs, and MCP tool calls — opt-in module, the instrumentation lives in the runtime itself, so you can see into a Kirak backend without bolting on a third-party APM.

Studio adds a dashboard, trace explorer, and alerts on top of this data
GET  ·  runtime instrumentation
GET/monitoring/pingliveness, no auth
GET/monitoring/healthstore health + queue depth
GET/monitoring/metricsOpenMetrics format
GET/monitoring/trace/{request_id}full request timeline
GET/monitoring/requests/timeseriestraffic + latency over time
GET/monitoring/ai/summaryAI runs, tokens, cost
GET/monitoring/mcp/summaryMCP calls per server + tool
POST/monitoring/alerts/evaluatethreshold checks for alerting
Served by the runtime

Encryption at rest

Social login tokens and MFA TOTP secrets are encrypted with AES-256-GCM before they touch the database, keyed by an env var you control and bound to the owning user's id. Other secrets live in a plain .env file, loaded once at startup, while every non-secret setting is in kirak.json — the open-source runtime has no secrets-vault CLI; that's a Kirak Studio console feature, layered on top.

Set your own key in production — falls back to a derived key with a startup warning otherwise
.env
KIRAK_AUTH_ENCRYPTION_KEY=…   # AES-256-GCM key for social tokens + MFA secrets
KIRAK_AUTH_JWT_SECRET_KEY=…   # required, >= 32 bytes
Real code you own

Hooks & custom endpoints

The one place you write real code. Business logic that isn't plain CRUD runs as before/after hooks on every operation, or as fully custom endpoints beyond the generated set. Sync hooks are blocking: they work like a pipeline — each takes the request, modifies it, and feeds it to the next hook or the operation, and raising an error stops the request with a clean 4xx. Async hooks are non-blocking: a failure or timeout is logged and skipped, so they suit side effects like notifications that must never fail the request. Real Python/FastAPI — scaffolded for you, committed to your own repo, surviving every later declaration change. It runs inside the runtime's access rules, so you extend the backend without opening a hole in it.

Python 3.10+ · FastAPI · no DSL, no proprietary abstraction
hooks/booking.py — yours to keep
# registered inside on_kirak_ready(kirak)

# sync hooks are a blocking pipeline: each takes the request, modifies it,
# and feeds it to the next hook or the operation -- raise to stop it
@kirak.on("booking").hook("before_create")
def check_dates(payload):
    data = payload["data"]
    if data["ends_at"] <= data["starts_at"]:
        raise ValidationError("ends_at must be after starts_at")  # 400
    return payload

@kirak.on("booking").hook("before_create")
def apply_deposit(payload):    # runs next, on check_dates' output
    payload["data"]["deposit_cents"] = payload["data"]["total_cents"] * 20 // 100
    return payload

# async hooks are non-blocking: a failure or timeout is logged
# and skipped -- it never fails the request
@kirak.on("booking").hook("after_create")
async def notify_owner(result):
    await kirak.notifications.send_im({"to": OWNER_CHANNEL, "text": "New booking"})
    return result
MySQLPostgreSQL

Database-agnostic

MySQL or PostgreSQL, switched with one line — "database": {"type": "postgres"} in kirak.json — not tied to a single vendor.

SOC 2

SOC 2-ready controls

Auth, RLS, encryption in transit and at rest, and audit logging built to help the APIs you ship pass a review. Kirak itself is not SOC 2 certified.

No lock-in

No lock-in, by construction

Every module opt-in and lazy-loaded. ~1 year of internal dogfooding across ~12 projects before this release.

Example declarations

Start from a working spec.

A public repository of model files and kirak.json for common patterns — copy one, adjust the fields, run it.

Auth starter

Email + password with MFA, SMS one-time codes, Google and GitHub social login, a users profile model, and owner-scoped access.

models/ · kirak.json · hooks/user.py
View on GitHub →

Stripe subscriptions

A plan / subscription model pair, Stripe checkout and webhook wiring, and an after_webhook hook that provisions access.

models/ · kirak.json · hooks/subscription.py
View on GitHub →

Blog with RLS

Posts, comments, and tags with public read, author-only write, and admin moderation — all expressed as access blocks, no policy code.

models/ · kirak.json
View on GitHub →

Multi-tenant SaaS

An org / membership model, tenant-scoped access on every model, per-org file storage, and a scheduled usage-rollup job.

models/ · kirak.json · hooks/usage.py
View on GitHub →
License

Apache License 2.0. Genuine open source.

OSI-approved. No license-level restriction on commercial use, forking, or offering Kirak as a competing hosted service.

What you can do

Copy, modify, distribute, and run Kirak for any purpose — including building a commercial product or a hosted service on top of it.

Patent grant + retaliation

Contributors grant a patent license for their contributions; anyone who sues over patent infringement loses their license to use the software.

Trademark

The license grants no right to the Kirak name or marks. A fork can rename and rebrand freely, but can't call itself "Kirak" or imply endorsement.

A fork must preserve copyright and attribution notices in the source and mark modified files as changed — it has no obligation to keep the Kirak name or credit Kirak in marketing. That's the deliberate trade-off of Apache 2.0 over a source-available license.

Community

Contributing & getting help.

GitHub Discussions

Questions, ideas, and design discussion — indexable and searchable, the durable record.

Open Discussions →

Discord

Real-time help and build-along chat with the team and other developers.

Join the Discord →

Contributing

Read CONTRIBUTING.md first — it covers the PR process and the policy on AI-generated contributions.

Read the guide →

Declare your backend. Run it yourself.

Ask about Kirak on