A compiled language
for APIs and services.
Carrier is a standalone compiled language for API services and Postgres-backed business logic. Routes, auth, CRUD, actions, transactions, policies, and jobs as first-class language constructs — lowered through a real compiler pipeline into a native service binary.
service DoctorDirectory { openapi { title: "Doctor Directory API" version: "0.2.0" } server { host: env("HOST", "0.0.0.0") port: env_int("PORT", 4001) }} auth jwt AdminAuth { issuer: env("JWT_ISSUER", "carrier") audience: env("JWT_AUDIENCE", "carrier-users") secret: env("JWT_SECRET", "local-dev-secret") roles_claim: "roles"} model Doctor { id: UUID slug: String @length(min: 3, max: 80) full_name: String @length(min: 3, max: 120) specialty: String rating: Float @range(min: 0, max: 5) status: DoctorStatus = active deleted_at: Time? created_at: Time updated_at: Time} crud Doctor at "/doctors" { create: protect AdminAuth roles [admin] list: public get: public update: protect AdminAuth roles [admin] delete: soft using deleted_at protect AdminAuth roles [admin] list_defaults { page_size: 20 sort: "rating" direction: desc scope: active } filters { specialty: exact city: exact language: contains } searchable { full_name specialty city bio }}Built narrow, so the backend gets out of the way.
Most backend frameworks give you a generic language plus conventions. Carrier inverts that. The backend patterns that actually matter — routes, auth, CRUD, policies, transactions, jobs — are language-level constructs the compiler understands.
.carrier source tree compiles through lexer, parser, semantic analysis, IR, Rust codegen, and Cargo into a native service binary. Rust is an implementation detail.crud generates list, get, create, update, delete, and restore — with filters, sort, pagination, search, soft delete, and explicit scopes.action blocks open Postgres transactions, lock rows with get_for_update, and call auth, cache, jobs, audit, and typed external clients.policy declarations produce PostgreSQL RLS SQL and runtime session context for carrier.current_roles and carrier.current_tenant.idempotent and Carrier stores the JSON response in Postgres, short-circuiting retries with the same Idempotency-Key.job, event, and schedule give you durable async work, cron-driven fan-out, and audit-backed event records.A real compiler, not a macro wrapper over a web framework.
Each stage has a clean responsibility and lives in its own crate. Carrier separates language surface from runtime, so the generated Rust service stays boring and reviewable.
.carrier files under src/. Every file compiles together, sorted by path..carrier/generated/ holds the Rust service, migrations, and the machine-readable manifest..carrier/build/<binary> is a native service. Tokio async server, /health, /ready, /openapi.json, /docs.Fifteen top-level constructs. That’s it.
Carrier’s entire declaration surface fits on one line. Every one of these is implemented today. Scope is the feature.
/// Transaction-safe admin action with row lockingaction audit_locked_doctor(id: UUID) -> Doctor { let actor = auth.current_user() transaction { let locked = Doctor.get_for_update(id: id, scope: "all") logs.info("doctor lock audit", { action_name: "audit_locked_doctor" doctor_id: locked.id actor_email: actor.email }) } return Doctor.get(id: id, scope: "all")} /// Idempotent admin route; retries return the stored responseroute POST "/admin/doctors/{id}/lock" protect AdminAuth roles [admin] idempotent -> Doctor{ params { id: UUID } handler { return audit_locked_doctor(params.id) }}policy AppointmentSlot { tenant_field: org_id read: roles [viewer, scheduler] write: roles [scheduler] deleted: roles [scheduler]} model AppointmentSlot { id: UUID org_id: String @index clinician_name: String @length(min: 3, max: 120) starts_at: Time status: SlotStatus = open version: Int @version deleted_at: Time? created_at: Time updated_at: Time}Postgres-first. Jobs, cache, idempotency, RLS — built in.
Generated services depend on the Carrier runtime for the boring, load-bearing parts you’d otherwise rebuild per project.
get_for_update, optimistic @version, and explicit scopes for soft-deleted records.policy compiles to PostgreSQL ENABLE ROW LEVEL SECURITY. Per request, the runtime sets carrier.current_roles and carrier.current_tenant.cache.get_as, cache.set, redis.publish. Uses Redis when REDIS_URL is healthy, otherwise an in-memory fallback.idempotent. Replays with the same Idempotency-Key return the stored response body without re-running the handler./openapi.json, /docs, and a first-class .carrier/manifest.json of declarations — AI-friendly, tooling-friendly.route GET "/search/doctors" public -> DoctorListResponse { query { q: String? specialty: String? city: String? country: String? page: Int = 1 per_page: Int = 20 sort: String = "rating" direction: String = "desc" scope: String = "active" } handler { let key = "doctor-search:" + json.stringify({ q: query.q, city: query.city, page: query.page }) if cache.exists(key) { return cache.get_as("DoctorListResponse", key) } let result = Doctor.search( q: query.q, city: query.city, page: query.page, per_page: query.per_page, sort: query.sort, direction: query.direction, scope: query.scope ) cache.set(key, result, ttl_seconds: 120) emit DoctorSearchCached { cache_key: key, scope: query.scope } return result }}event SlotReminderQueued { slot_id: UUID trigger: String} job send_slot_reminder(payload: Json) -> Void { logs.info("slot reminder job", { job_name: "send_slot_reminder", payload: payload }) audit.record("send_slot_reminder", "AppointmentSlot", "scheduled", payload)} schedule "*/15 * * * *" run send_slot_reminder route POST "/slots/{id}/notify" protect StaffAuth roles [scheduler] idempotent -> Json{ params { id: UUID } handler { let job_id = jobs.enqueue("send_slot_reminder", { slot_id: params.id }, delay_seconds: 5) emit SlotReminderQueued { slot_id: params.id, trigger: "manual" } return { job_id: job_id } }}- Declarations
- 15
- top-level constructs
- Built-ins
- string · array · json · cache · sql · db · logs · audit · jobs · redis
- typed namespaces
- Artifacts
- OpenAPI · manifest · migrations · binary
- Examples
- 4
- from minimal to doctor-directory
Precise enough for humans. Constrained enough for agents.
Carrier’s advantage isn’t maximal language power. It’s that the backend surface is small, explicit, and compiler-checked — exactly the shape that lets code generators stay consistent across a project.
.carrier/manifest.json with models, routes, policies, jobs, schedules, and clients. Tools read it instead of scraping source.carrier check runs the full semantic pass: type errors, CRUD misuse, auth/policy validation, invalid idempotency scope, and more.00_models, 10_types, 20_actions, 30_routes) keeps generation order stable across agents.// docs/ai-authoring.md — fast generation recipe1. write carrier.toml2. declare one service3. add auth jwt if any route is protected4. create enum used by models or query defaults5. create model declarations6. create type declarations for I/O and pagination7. add crud for resource-like models8. add policy for role or tenant visibility9. add action for real business writes10. add custom route11. add job / event / schedule only when needed12. add client for outbound integrations13. add pure fn helpers lastFour checked-in examples. Ladder from minimal to platform.
Every example in the repository compiles today. They’re ordered so you can learn the language by walking up the ladder.
Minimal single-file service. JWT auth, typed I/O, four routes.
Recommended multi-file production shape with role-only policy.
Tenant-aware slots, optimistic @version, jobs, events, schedules.
Full platform showcase: cache, SQL, DB functions, clients, audit.
/// Admin reporting endpoint using raw SQL mapped into a typed result listroute GET "/reports/doctors/by-city" protect AdminAuth roles [admin] -> DoctorCityReportRow[]{ handler { return sql.list_as( "DoctorCityReportRow", "select city, count(*)::bigint as total
from doctors
where deleted_at is null
group by city
order by total desc, city asc" ) }}client WeatherApi { base_url: env("WEATHER_API_URL", "https://api.example.com") header "x-api-key": env("WEATHER_API_KEY", "") timeout_ms: 3000} type WeatherResponse { city: String temperature_c: Float condition: String} route GET "/weather/{city}" public -> WeatherResponse { handler { return WeatherApi.get("/forecast", query: { city: params.city }) }}From zero to a native binary in three commands.
The compiler workspace is a Rust project; Carrier services compile through it.
$ cargo test$ cargo run -p carrierc -- --helpBuild and test the compiler. The carrierc CLI scaffolds projects, type-checks .carrier sources, generates Rust + OpenAPI + migrations, and drives build, run, and dev.
# new project, then in the project root:$ carrier check$ carrier build$ carrier run # or ./.carrier/build/<binary>$ carrier openapi > openapi.json$ carrier migrate generateWorks identically across every example in the repo — hello-carrier, inventory-control, booking-service, and the flagship api/.
transaction { } opens a real DB transaction. return inside the block is not supported yet — return after the block closes.Backend work as a language problem,
not a framework problem.
Read the language reference, walk the examples, and ship a compiled service.