Arkveil

Getting started

Seed the demo access model, run its policy tests, and protect your first NestJS endpoint with real granted and denied decisions.

This walkthrough takes one endpoint from unprotected to policy-enforced. You will seed a demo access model, run the tests that specify its behavior, and wire a NestJS route to live decisions. Everything runs against Arkveil Cloud, so there is nothing extra to deploy.

You need three things. Node.js 18 or newer. An Arkveil account — register in Arkveil Studio and confirm your email. A NestJS application to protect — an empty one works fine.

Install the CLI and log in

npm install -g @arkveil/cli
arkveil auth login

The login opens your browser and completes a device authorization flow — sign in with your Arkveil account, or approve right away if the browser is already signed in. The flow signs you into an account that already exists, which is why registering comes first. Credentials land in your OS keychain, and arkveil auth whoami shows who you are logged in as. Details live in the CLI authentication reference.

Seed the demo model

A new workspace starts empty. Seed the small billing demo that this walkthrough builds on:

arkveil admin seed-demo

Seeding requires an empty workspace. If you already created entities, clear the workspace first with arkveil admin clear — running seed-demo on a non-empty workspace is refused, and nothing is created.

The demo models an invoice service. Look around:

arkveil trees all
ACTION_POLICIES
├─ • Admin access [TARGET]
├─ • Billing operations [TARGET]
└─ • View and edit invoices [TARGET]

ACTIONS
├─ • View invoice [ACTION]
├─ • Edit invoice [ACTION]
├─ • Issue invoice [ACTION]
└─ • Assign user role [ACTION]

TESTS
├─ • Manager can edit an invoice [TEST]
├─ • Manager cannot issue an invoice [TEST]
└─ … 12 more

Actions are the operations your application will ask about. Policies grant access to them, and tests pin the intended behavior. All of it exists before a single line of integration code.

Run the policy tests

arkveil tests run-all
RUN ID                                STATUS  PASSED  FAILED  ERRORED
────────────────────────────────────  ──────  ──────  ──────  ───────
76a1e8d9-f324-42ce-a3a5-af377977494c  PASSED  1/1     0       0
f3041092-6860-4ee8-b960-e49e8435418a  PASSED  1/1     0       0

Every seeded test should pass. Each one supplies a scenario — user attributes, request parameters, sometimes database fixtures — and asserts the expected decision. This is the model's safety net while you change policies later.

Create an API key

Your application authenticates its decision requests with a workspace API key:

arkveil keys create

The secret is shown once, so save it now. Export it where your application can read it:

export ARKVEIL_API_KEY="akv_…"

Protect an endpoint

Install the NestJS SDK in your application:

npm install @arkveil/nest

Register the module once, in your root module:

import { Module } from "@nestjs/common";
import { ArkveilModule } from "@arkveil/nest";
import { InvoicesController } from "./invoices.controller";

@Module({
  imports: [
    ArkveilModule.forRoot({
      serviceUrl: "https://api.arkveil.com",
      apiKey: process.env.ARKVEIL_API_KEY!,
      // Arkveil decides what an authenticated user may do. Attributes normally
      // come from your auth layer. The walkthrough fakes them with a header.
      getUserAttributes: (req) => JSON.parse(String(req.headers["x-user"] ?? "{}")),
    }),
  ],
  controllers: [InvoicesController],
})
export class AppModule {}

Generate the typed integration file. It merges your workspace's permission codes and attribute schemas into the SDK's types:

arkveil generate typescript -o src/arkveil.generated.ts

Now guard the route. One decorator marks the enforcement point, and the permission code is type-checked against the generated file:

import { Controller, Param, Patch } from "@nestjs/common";
import { PermissionPoint } from "@arkveil/nest";
import "./arkveil.generated";

@Controller("invoices")
export class InvoicesController {
  @Patch(":id")
  @PermissionPoint("invoices:edit")
  edit(@Param("id") id: string) {
    return { id, status: "updated" };
  }
}

There is no role check and no if statement here. The route only declares which action it is.

See the decisions

Start the application and call the endpoint as a manager:

curl -X PATCH http://localhost:3000/invoices/inv-1 \
  -H 'x-user: {"id":"u-42","role":"manager","region":"EU"}'
{"id":"inv-1","status":"updated"}

Now as a user with no granting policy:

curl -X PATCH http://localhost:3000/invoices/inv-1 \
  -H 'x-user: {"id":"u-7","role":"viewer"}'
{"message":"You do not have permission to perform this action","error":"Forbidden","statusCode":403}

The first request was granted by the seeded policy that lets managers edit invoices. The second found no policy granting invoices:edit to a viewer, and access is denied unless a policy explicitly grants it.

Explain a decision

Every decision can be explained without touching the application:

arkveil eval explain -a invoices:edit --user '{"id":"u-7","role":"viewer"}'
action:             invoices:edit
granted:            DENIED
granting policies:  (none)
candidate policies: 4

Four policies were candidates and none granted access. The full trace — every candidate policy and how its condition evaluated — lives in Arkveil Studio.

Next steps

  • Coding agents — hand the next rule to your coding agent.
  • Access model — the concepts behind what you just used.
  • SDK — the TypeScript SDK packages, including row-level data protection.
  • CLI — authentication, configuration, and the full command reference.

You protected an action. The same model also constrains which data operations may touch, and that is where the access model pays off in depth — data policies compile into SQL enforced with your own queries and transactions.

On this page