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 — the right mode for a first experiment.

You need three things. Node.js 20 or newer. An Arkveil account — register in Arkveil Studio and confirm your email. A NestJS application to protect — NestJS 8 through 11, an empty one works fine. If you are starting fresh, npx @nestjs/cli@11 new scaffolds a compatible app.

Install the CLI and log in

npm install -g @arkveil/cli
arkveil 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 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 model all
ACTION_POLICIES
├─ • Admin access [TARGET] 589479af-5edf-4477-9b7f-24effc07bf67
├─ • Billing operations [TARGET] 309ca4a1-1aa2-413e-8b5f-106d0dbcc4e1
└─ • View and edit invoices [TARGET] 7069057c-6772-4c37-92ef-be27f9002f02

DATA_POLICIES
├─ • Invoices [TARGET] f133c1d8-4d74-4a16-bd79-880fb54d9088
├─ • Payments [TARGET] 4386e393-4ded-4da7-811d-9a15c0911217
└─ • Admin access [TARGET] 596fc895-db79-4aa3-a048-76126b323176

ACTIONS
├─ • View invoice [ACTION] 3d5e0350-a3b5-461f-b29b-1ed5585abcc9
├─ • Edit invoice [ACTION] 4cf2a80c-bbe2-484f-8312-df57f8e09397
├─ • Issue invoice [ACTION] e959ddf3-47e9-463a-bd45-2bb3ab5e7ced
└─ • Assign user role [ACTION] bb04cb5a-cdd2-43b3-a40e-a4f21c88aee9

TESTS
├─ • Admin can assign user roles [TEST] 0b9242c9-f403-405b-bf94-4f3371461ee5
├─ • Finance user can issue an invoice [TEST] 9bb00289-99b1-4fd5-88b7-c6811f8de5d5
└─ … 12 more

DATASOURCES
└─ • demo_billing [DATASOURCE] da328210-0a00-46b4-b7f0-0633eaa6e234
   ├─ • Invoices [DATASET] 4e21905f-6ab8-40ea-8e14-b4c3b25d4003
   └─ • Payments [DATASET] 7d68f66f-d819-4298-a7c1-d8e9d8846740

Actions are the endpoints or other functions your application will ask permission for. Targets decide where policies apply — one action, a tagged group, or everything. Policies grant access within their target's scope, and tests pin the intended behavior. Data policies and datasources are the data side of the model — this walkthrough stays on actions. 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  TRIGGERED
────────────────────────────────────  ──────  ──────  ──────  ───────  ──────────────────────────────
f711b86b-fe89-4e31-ac19-49dfc065bfb0  PASSED  1/1     0       0        2026-09-14T01:19:09.380404652Z
f3c6d3de-a368-46f5-8709-7cd550bf88e1  PASSED  1/1     0       0        2026-09-14T01:19:09.544191258Z
…

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.
  • Runtime — when this graduates to production: the same decision API, self-hosted in your infrastructure, one serviceUrl change away.

You protected an action. The same model also decides which rows may be seen, touched or produced, 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