Skip to content
KRPUS
Sign up

DOCS

Connect

Start from the app, not the mechanism. Every connection is one app holding one credential for one door, and Korpus picks the way in that suits the app: a sign-in for anything with a browser, a trust for a job on GitHub Actions or AWS, a key for the rest.

Overview

Accessme is your own access: your full rights, signed in through the browser, never a key and never a trust. Every other access is a delegation (nightly-sync, ci-bot): a name, a job and path rules that say what it may create, read, update and delete. An AI agent, an automation, a script and an SDK program are all delegations. Every write is signed @you via <delegation>.
DoorsThe API is for programs: the web app, the SDK, the CLI, scripts, CI. MCP is for AI apps: Claude, ChatGPT, Claude Code, Cursor, LiteLLM, AgentCore. A delegation works at either door; one connection is for one.
ConnectionOne app, one credential, one door, for me or for one delegation. The Access page in the web app lists them for each access with the app, door, way in, when made and last used, and Revoke ends one at once.
Ways inSign in (OAuth, or a device code on a box with no browser), trust (a GitHub Actions or AWS identity traded for an hour-long token) and key (kp_…, a bearer or a client secret).
In the appOn Access, Connect opens the same tiles as below, with your username and delegation filled in.

Addresses

Everything hangs off one host, fra.korpus.cloud here, and nothing else names a region.

Web apphttps://fra.korpus.cloud
APIhttps://api.fra.korpus.cloud/v1/<op>: POST with a JSON body (The API).
OAuthhttps://api.fra.korpus.cloud/oauth/* and https://api.fra.korpus.cloud/.well-known/oauth-authorization-server: one issuer for both doors.
MCP, for mehttps://mcp.fra.korpus.cloud/: the me of whoever signs in. Or for one account, https://mcp.fra.korpus.cloud/<username>/me.
MCP, for a delegationhttps://mcp.fra.korpus.cloud/<username>/<delegation>
Trust audiencehttps://api.fra.korpus.cloud/<username>/<delegation>: what a platform token must name (Trust a workload).

A username is chosen once and never changes, so none of these addresses ever moves. Every credential is bound to the door it was made for; at the other one it is refused (The wrong door).

Connect an app

Replace <username> with your username and <delegation> with the delegation. As me, the apps that sign in (Claude, ChatGPT, Claude Code, Cursor and any other MCP client) take https://mcp.fra.korpus.cloud/<username>/me in place of the delegation's address; everything else needs a delegation.

AI apps MCP

Claude Sign in

Add Korpus in Claude. Add a custom connector with this name and URL, then press Connect.

Settings → Connectors → Add custom connector

Open Claude settings

Name

korpus-<username>-<delegation>

Server URL

https://mcp.fra.korpus.cloud/<username>/<delegation>

ChatGPT Sign in

Add Korpus in ChatGPT. With developer mode on (under Advanced settings, on the plans that have it), create a connector with this name and URL. ChatGPT asks you to sign in.

Settings → Apps & Connectors → Create

Name

korpus-<username>-<delegation>

Server URL

https://mcp.fra.korpus.cloud/<username>/<delegation>

Claude Code Sign in

Add Korpus to Claude Code. In your project's folder:

claude mcp add --transport http korpus-<username>-<delegation> https://mcp.fra.korpus.cloud/<username>/<delegation>

Sign in from Claude Code. In Claude Code, type /mcp, choose korpus-<username>-<delegation> and Authenticate: your browser opens Korpus.

Another way: Key

Create a key first (Keys, below). It is shown once; replace $KORPUS_KEY with it.

Add Korpus to Claude Code. With the key as a header, read from KORPUS_KEY:

claude mcp add --transport http korpus-<username>-<delegation> https://mcp.fra.korpus.cloud/<username>/<delegation> --header "Authorization: Bearer $KORPUS_KEY"

.mcp.json

{"mcpServers":{"korpus-<username>-<delegation>":{"type":"http","url":"https://mcp.fra.korpus.cloud/<username>/<delegation>","headers":{"Authorization":"Bearer ${KORPUS_KEY}"}}}}

Cursor and VS Code Sign in

Add Korpus to your editor. Your editor opens and asks to install the server, then signs in through your browser. Or add it to .cursor/mcp.json by hand.

.cursor/mcp.json

{"mcpServers":{"korpus-<username>-<delegation>":{"type":"http","url":"https://mcp.fra.korpus.cloud/<username>/<delegation>"}}}

LiteLLM Key as client credentials

Create a key first (Keys, below). It is shown once; replace $KORPUS_KEY with it.

Add Korpus in LiteLLM. Add an MCP server at this URL with OAuth client credentials: the client id below, the key as the secret.

Server URL

https://mcp.fra.korpus.cloud/<username>/<delegation>

Token URL

https://api.fra.korpus.cloud/oauth/token

Client id

<username>/<delegation>
curl -s https://api.fra.korpus.cloud/oauth/token -d grant_type=client_credentials -d client_id=<username>/<delegation> -d client_secret="$KORPUS_KEY" --data-urlencode resource=https://mcp.fra.korpus.cloud/<username>/<delegation>

AgentCore Gateway Key as client credentials

Create a key first (Keys, below). It is shown once; replace $KORPUS_KEY with it.

Add Korpus in AgentCore Gateway. Add this URL as an MCP target with OAuth client credentials: the client id below, the key as the secret.

Server URL

https://mcp.fra.korpus.cloud/<username>/<delegation>

Token URL

https://api.fra.korpus.cloud/oauth/token

Client id

<username>/<delegation>
curl -s https://api.fra.korpus.cloud/oauth/token -d grant_type=client_credentials -d client_id=<username>/<delegation> -d client_secret="$KORPUS_KEY" --data-urlencode resource=https://mcp.fra.korpus.cloud/<username>/<delegation>

Another MCP client Key

Create a key first (Keys, below). It is shown once; replace $KORPUS_KEY with it.

Add Korpus to your app. Send the key as a bearer token, read from KORPUS_KEY.

Server URL

https://mcp.fra.korpus.cloud/<username>/<delegation>

Header

Authorization: Bearer $KORPUS_KEY

Programs API

SDK Sign in by device code

Sign in from the terminal. Install the Korpus CLI and sign in. It prints a link and a short code: open the link and allow <delegation>.

npm install -g @korpus-cloud/sdk
korpus login <delegation> --user <username>

Use it in a program. In the program, install the SDK; it finds the sign-in, no key:

npm install @korpus-cloud/sdk

Code

import { Korpus } from '@korpus-cloud/sdk';

const korpus = new Korpus({ delegation: '<delegation>', baseUrl: 'https://api.fra.korpus.cloud' });
const bundles = await korpus.listBundles();
console.log(bundles);
Another way: Key

Create a key first (Keys, below). It is shown once; replace $KORPUS_KEY with it.

Use it in a program. Install the SDK. It reads the key from KORPUS_KEY, set where the program runs:

npm install @korpus-cloud/sdk

Code

import { Korpus } from '@korpus-cloud/sdk';

const korpus = new Korpus({ token: process.env.KORPUS_KEY, baseUrl: 'https://api.fra.korpus.cloud' });
const bundles = await korpus.listBundles();
console.log(bundles);

CLI Sign in by device code

Sign in from the terminal. Install the Korpus CLI and sign in. It prints a link and a short code: open the link and allow <delegation>.

npm install -g @korpus-cloud/sdk
korpus login <delegation> --user <username>
Another way

Sign in with curl. Without Node, curl and jq do the same:

R=$(curl -s https://api.fra.korpus.cloud/oauth/device_authorization -d client_id=korpus-cli --data-urlencode resource=https://api.fra.korpus.cloud/<username>/<delegation>)
echo "Open $(jq -r .verification_uri_complete <<<"$R") and allow <delegation>"
while :; do
  T=$(curl -s https://api.fra.korpus.cloud/oauth/token -d grant_type=urn:ietf:params:oauth:grant-type:device_code -d client_id=korpus-cli -d device_code="$(jq -r .device_code <<<"$R")")
  case "$T" in *authorization_pending*|*slow_down*) sleep 5 ;; *) echo "$T"; break ;; esac
done

GitHub Actions Trust

Trust the repository or the roles first (Trust a workload, below).

Add the step to your workflow. Let the job ask GitHub for a token, then add this step before anything that uses Korpus. Later steps find the hour-long token in KORPUS_ACCESS_TOKEN.

permissions

permissions:
  id-token: write
  contents: read

step

- name: Korpus token
  run: |
    JWT=$(curl -sH "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https%3A%2F%2Fapi.fra.korpus.cloud%2F<username>%2F<delegation>" | jq -r .value)
    KORPUS_ACCESS_TOKEN=$(curl -s https://api.fra.korpus.cloud/oauth/token -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange -d subject_token_type=urn:ietf:params:oauth:token-type:jwt --data-urlencode "subject_token=$JWT" | jq -er .access_token)
    echo "::add-mask::$KORPUS_ACCESS_TOKEN"
    echo "KORPUS_ACCESS_TOKEN=$KORPUS_ACCESS_TOKEN" >> "$GITHUB_ENV"

AWS Trust

Trust the repository or the roles first (Trust a workload, below).

Turn on outbound federation. Once per account. It prints the issuer the trust asks for; the role to trust is arn:aws:iam::<account>:role/korpus-<delegation>.

aws iam enable-outbound-web-identity-federation; aws iam get-outbound-web-identity-federation-info

Write the function. A Lambda function on the SDK: it mints the role's token, trades it and renews it, no key.

npm install @korpus-cloud/sdk @aws-sdk/client-sts

src/handler.ts

import { Korpus } from '@korpus-cloud/sdk';

// KORPUS_AUDIENCE and AWS_REGION are all it needs: the SDK mints the role's
// web identity token, trades it for an hour-long Korpus token and renews it.
const korpus = new Korpus();

export const handler = async () => {
  const bundles = await korpus.listBundles();
  return bundles.map((bundle) => bundle.name);
};

Deploy it. Deploy it with the CDK; it makes the role korpus-<delegation>.

CDK

import { CfnOutput, Duration } from 'aws-cdk-lib';
import { ManagedPolicy, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';

// In the stack:
const role = new Role(this, 'KorpusRole', {
  roleName: 'korpus-<delegation>', // the role Korpus trusts
  assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
  managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')],
});
role.addToPolicy(
  new PolicyStatement({
    actions: ['sts:GetWebIdentityToken'],
    resources: ['*'],
    conditions: {
      'ForAllValues:StringEquals': { 'sts:IdentityTokenAudience': 'https://api.fra.korpus.cloud/<username>/<delegation>' },
      NumericLessThanEquals: { 'sts:DurationSeconds': 300 },
    },
  }),
);
new NodejsFunction(this, 'KorpusFunction', {
  entry: 'src/handler.ts',
  runtime: Runtime.NODEJS_22_X,
  role,
  timeout: Duration.seconds(30),
  environment: { KORPUS_AUDIENCE: 'https://api.fra.korpus.cloud/<username>/<delegation>' },
  // The SDK loads @aws-sdk/client-sts when it first mints: ship it beside the bundle.
  bundling: { nodeModules: ['@aws-sdk/client-sts'] },
});
new CfnOutput(this, 'KorpusRoleArn', { value: role.roleArn });
Another way

Bundle the function. Bundle the handler, with @aws-sdk/client-sts beside it:

npx esbuild src/handler.ts --bundle --platform=node --format=esm --outfile=dist/index.mjs
cd dist && npm init -y && npm install @aws-sdk/client-sts && cd ..

Apply it. Then terraform apply:

main.tf

locals {
  korpus_audience = "https://api.fra.korpus.cloud/<username>/<delegation>"
}

data "aws_iam_policy_document" "korpus_assume" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }
  }
}

data "aws_iam_policy_document" "korpus" {
  statement {
    actions   = ["sts:GetWebIdentityToken"]
    resources = ["*"]
    condition {
      test     = "ForAllValues:StringEquals"
      variable = "sts:IdentityTokenAudience"
      values   = [local.korpus_audience]
    }
    condition {
      test     = "NumericLessThanEquals"
      variable = "sts:DurationSeconds"
      values   = ["300"]
    }
  }
}

resource "aws_iam_role" "korpus" {
  name               = "korpus-<delegation>" # the role Korpus trusts
  assume_role_policy = data.aws_iam_policy_document.korpus_assume.json
}

resource "aws_iam_role_policy" "korpus" {
  role   = aws_iam_role.korpus.id
  policy = data.aws_iam_policy_document.korpus.json
}

resource "aws_iam_role_policy_attachment" "korpus_logs" {
  role       = aws_iam_role.korpus.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

data "archive_file" "korpus" {
  type        = "zip"
  source_dir  = "dist"
  output_path = "korpus.zip"
}

resource "aws_lambda_function" "korpus" {
  function_name    = "korpus-<delegation>"
  role             = aws_iam_role.korpus.arn
  runtime          = "nodejs22.x"
  handler          = "index.handler"
  timeout          = 30
  filename         = data.archive_file.korpus.output_path
  source_code_hash = data.archive_file.korpus.output_base64sha256
  environment {
    variables = { KORPUS_AUDIENCE = local.korpus_audience }
  }
}

output "korpus_role_arn" {
  value = aws_iam_role.korpus.arn
}
Another way

Allow the role. Give the role this one permission:

Role policy

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"sts:GetWebIdentityToken","Resource":"*","Condition":{"ForAllValues:StringEquals":{"sts:IdentityTokenAudience":"https://api.fra.korpus.cloud/<username>/<delegation>"},"NumericLessThanEquals":{"sts:DurationSeconds":300}}}]}

Mint and trade. In the workload, mint a token, then trade it:

JWT=$(aws sts get-web-identity-token --audience https://api.fra.korpus.cloud/<username>/<delegation> --signing-algorithm ES384 --duration-seconds 300 --query WebIdentityToken --output text)
KORPUS_ACCESS_TOKEN=$(curl -s https://api.fra.korpus.cloud/oauth/token -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange -d subject_token_type=urn:ietf:params:oauth:token-type:jwt --data-urlencode "subject_token=$JWT" | jq -er .access_token)

Another HTTP client Key

Create a key first (Keys, below). It is shown once; replace $KORPUS_KEY with it.

Point it at Korpus. Send the key as a bearer token with every request. To check it, ask what it may call:

API URL

https://api.fra.korpus.cloud/v1

Header

Authorization: Bearer $KORPUS_KEY
curl -s https://api.fra.korpus.cloud/v1 -H "Authorization: Bearer $KORPUS_KEY"

Not here? Anything that speaks MCP over HTTP takes the generic MCP recipe; anything that can send an HTTP header takes the API one. The ops either door serves are in The API.

Sign in

For an app that has a browser, or a person at a terminal. OAuth 2.1 with PKCE: the app is shown the address, you sign in to the web app and press Allow on a page that names the app and, for a delegation, its job and rules. A client with MCP OAuth support needs nothing configured: it discovers everything from the first 401. The result is an access token (1 hour) and a refresh token (30 days, rotated on every use; one presented twice ends the whole connection).

  1. Discovery. An unauthenticated MCP request answers 401 with WWW-Authenticate: Bearer resource_metadata="…" (RFC 9728). The authorization server's metadata is at https://api.fra.korpus.cloud/.well-known/oauth-authorization-server (RFC 8414).
  2. Registration. POST /oauth/register (RFC 7591), public clients only: token_endpoint_auth_method is none. Redirect URIs are https, or http on localhost, 127.0.0.1 or [::1]. An unused registration expires after 90 days. Or none: a client whose client_id is an https URL is read from that Client ID Metadata Document (no redirects, at most 5 KB, believed only when it names itself and its redirect URIs pass the same rule) and remembered for a day.
  3. Authorize. GET /oauth/authorize with response_type=code, PKCE S256, scope mcp and resource, the address the token is for: it decides the door and the access. The request is good for 10 minutes; the code for 5.
  4. Token. POST /oauth/token with authorization_code or refresh_token.

Each approval is one connection under its access on the Access page. Revoke ends it at once, and the next call answers 401. One unused for 30 days ends with its refresh token; deleting the delegation ends all of its connections.

Device code

For a box with no browser (a server, a runner, a terminal over SSH): RFC 8628, for a delegation only, never me; the recipes here use it at the API door (resource names the door). The korpus command does all of it (The CLI); the connect recipes above print it, and the same by hand with curl and jq. By hand:

  1. POST /oauth/device_authorization with client_id=korpus-cli (or a registered client) and resource=https://api.fra.korpus.cloud/<username>/<delegation>. It answers device_code, user_code (BCDF-GHJK), verification_uri and verification_uri_complete (the web app's /device page), expires_in 600 and interval 5.
  2. You open that page anywhere, sign in, check the code, see the delegation's rules and press Allow. A code works once.
  3. The device polls POST /oauth/token with grant_type=urn:ietf:params:oauth:grant-type:device_code, device_code and client_id: authorization_pending until you decide, slow_down when it polls faster than the interval, then the same access and refresh tokens as a browser sign-in (or access_denied, expired_token).

Trust a workload

A job on GitHub Actions or AWS signs in as a delegation with nothing secret stored anywhere. You trust the repository or the roles once; the job trades the token its platform already gives it for a Korpus token that lasts an hour.

WhoA delegation, never me. At the API door only: a trust gives no MCP connection.
TrustedGitHub: one repository, held by its ids, pinned to an environment, a ref or a written pattern. AWS: IAM roles of one account. At most 10 per delegation.
AudienceEvery platform token must be minted for the delegation's API address, https://api.fra.korpus.cloud/<username>/<delegation>: this deployment's host, your username and the delegation. A token for one deployment trades at no other. The Access page and add_trust print it.
Tokenkat_…, one hour, no refresh token: trade a fresh platform token for the next. Send it as Authorization: Bearer to the API. KORPUS_ACCESS_TOKEN is a place to keep it for curl and your own scripts; the SDK does not read it, it trades for its own.
OrderPrefer it to a key, which works for anyone who has it.

GitHub Actions

  1. Access → the delegation → Trust GitHub or AWS → GitHub Actions. Enter the repository (owner/name) and, best, an environment. For a private repository, add its ids: gh api repos/owner/name --jq ".id, .owner.id". Or ask your AI app as me: add_trust provider=github repository=owner/name environment=production.
  2. In the workflow, give the job id-token: write and add the step shown under GitHub Actions above. It masks the token and puts it in KORPUS_ACCESS_TOKEN for the later steps.
  3. Protect the environment: required reviewers or branch rules. It becomes your gate: a job that may not deploy to it cannot get a Korpus token either.

With the SDK the step is not needed: new Korpus() finds the job's token and trades it itself, given the audience.

    permissions:
      id-token: write
    env:
      KORPUS_AUDIENCE: https://api.fra.korpus.cloud/<username>/<delegation>
    steps:
      - run: node sync.js   # new Korpus() trades the job's token itself

A GitHub trust is always pinned. An environment is best; a ref (refs/heads/main, refs/tags/v*) pins one branch or tag pattern. To let every workflow of the repository sign in, pull requests from its branches included (GitHub gives no token to pull requests from forks), write it out: sub_pattern=repo:owner/name:*. Anyone who may push a branch could then act as the delegation.

AWS

IAM outbound identity federation lets a role ask AWS STS for a signed token about itself. Nothing is stored in the workload; the role's own credentials ask.

  1. Once per AWS account, the first command under AWS above; it prints the account issuer, https://….tokens.sts.global.api.aws.
  2. Access → the delegation → Trust GitHub or AWS → AWS. Paste the issuer and the role ARNs, one per line. One role acts as one of your delegations.
  3. Give each role the one permission shown under AWS, pinned to the delegation and to five-minute tokens.

In AWS CDK, the same policy on a Lambda function's role, the audience for the SDK, and the ARN to trust as a stack output (for another role, role.addToPolicy with the same statement, and KORPUS_AUDIENCE in its environment):

import { CfnOutput } from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';

const audience = 'https://api.fra.korpus.cloud/<username>/<delegation>';

fn.addToRolePolicy(new iam.PolicyStatement({
  actions: ['sts:GetWebIdentityToken'],
  resources: ['*'],
  conditions: {
    'ForAllValues:StringEquals': { 'sts:IdentityTokenAudience': audience },
    NumericLessThanEquals: { 'sts:DurationSeconds': 300 },
  },
}));
fn.addEnvironment('KORPUS_AUDIENCE', audience); // the SDK reads it

new CfnOutput(this, 'KorpusRoleArn', { value: fn.role!.roleArn }); // the ARN to trust

Ask regional STS for the token (GetWebIdentityToken is not on the global endpoint). With the SDK and @aws-sdk/client-sts installed, set KORPUS_AUDIENCE and new Korpus() mints and trades by itself, renewing before the hour ends.

The exchange

The same for both: RFC 8693 token exchange at the token endpoint, the platform token in $JWT: POST https://api.fra.korpus.cloud/oauth/token with grant_type=urn:ietf:params:oauth:grant-type:token-exchange, subject_token_type=urn:ietf:params:oauth:token-type:jwt and the platform token as subject_token. No client, no secret.

{ "access_token": "kat_…",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "token_type": "Bearer",
  "expires_in": 3600 }

Refusals answer 400 with error: invalid_grant and an error_description that names the reason.

What Korpus checks

  • The token's aud is one delegation's audience of this deployment, and a trust of that delegation names the token's issuer. Korpus asks only that issuer for its keys, and only after the match.
  • The signature, against the issuer's published keys; exp; and iat within the last 10 minutes.
  • AWS: the token's sub is one of the trusted role ARNs, of the trusted account. GitHub: repository_id and repository_owner_id are the trusted repository's, and sub matches the environment, ref or pattern.
  • Each platform token trades once. A copy taken from a log is worth nothing once its job used it.
  • The delegation still exists and the account is not blocked. Removing the trust or deleting the delegation stops the tokens it minted at their next request.

List and remove

A trust is one connection of the delegation: the Access page lists it with its setup again and Remove trust. As me over MCP or the API: list_connections and revoke_connection. Korpus records every exchange in its audit trail, with the trust and the workload (role ARN or GitHub subject) that made it.

Troubleshooting

aud must be one delegation's audienceMint the token for the audience Korpus printed, exactly, and only that one.
no trust … names issuerAWS: the issuer pasted is another account's. GitHub: the trust is for another delegation.
is not a trusted roleThe role ARN differs (path included) from the one trusted. An assumed role's token names the role, not the session.
another repositoryThe repository was deleted and made again under its name, or the ids given were wrong. Remove the trust and add it again.
subject … does not matchThe job runs outside the trusted environment or ref.
too old / already exchangedMint a new platform token right before the exchange, one per exchange.

Keys

The last resort, for a host that can only send a fixed header (n8n, the Claude API's mcp_servers, Managed Agents) or that fetches its own tokens with OAuth client credentials (AgentCore Gateway, LiteLLM, Strands). A key works for anyone who has it until it ends: keep it in a secret store, never in a repository.

ForA delegation, never me. At one door: the key you make for the API is refused at MCP and the other way round.
Shapekp_<deployment>_<43 random characters><6 checksum characters>. The deployment label (fra, dev.fra) tells the SDK and the CLI where it belongs, so a key needs no URL; the checksum lets a secret scanner tell one from noise. Stored only as a hash.
MadeOn the Access page, Create key on the delegation, or create_key. Shown once. Expires after 30, 90, 365 days, and stops working after 90 days unused. At most 5 per delegation.
BearerSend it on every request as Authorization: Bearer kp_…. Keep KORPUS_KEY in the environment; a checked-in .mcp.json can read it from there.
Client credentialsPOST https://api.fra.korpus.cloud/oauth/token with the client_credentials grant, client_id=<username>/<delegation> and the key as client_secret (in the body, or HTTP Basic), and resource naming the door. It answers an hour-long token and no refresh token; the host fetches the next itself.
RevokedRevoke on the Access page, revoke_connection or deleting the delegation ends it at once. The log names the key by its first 8 characters.

The wrong door

Every credential is bound to one door and one address: an API credential works at https://api.fra.korpus.cloud/v1 only, an MCP credential at its own MCP address only, and a delegation's at its own account's address, never me's or another delegation's. Anywhere else the answer is one error, 403 wrong_audience, and it names where the credential does work.

HTTP/1.1 403
{ "error": { "code": "wrong_audience",
             "message": "this credential is for delegation \"nightly-sync\" at https://api.fra.korpus.cloud/v1; use that address",
             "url": "https://api.fra.korpus.cloud/v1" } }

Over MCP the same refusal is a JSON-RPC error -32003 with the HTTP status. The usual cause is a key made for one door and pasted at the other, or a username mistyped in an address. The fix is the url in the answer, or a new connection at the right door.

Delegations and rights

At most 50 delegations per account, 50 rules each; names are 1–32 characters of a-z 0-9 . _ -. me is built in, fixed, and holds your full rights; nothing creates, changes or deletes it. For a reviewer of the catalog, me also carries the review ops; a delegation never does.

Rules

{ "bundle": "<id> | *", "path": "<pattern>", "rights": ["create", "read", "update", "delete"] }
  • read is always implied.
  • Patterns: * alone is the whole bundle; * or ** as the last segment is everything below that folder, at any depth (the two mean the same there); ** elsewhere is any number of folders; * inside a segment matches within that segment. No .md.
  • Rules only subtract: your role in the bundle is checked first.
  • Rules are read on every request, so a change holds from the next call.
PatternMatchesDoes not match
notes/*notes/a, notes/a/bnotes, other/notes/a
**/test/*test/a, x/y/test/a/btest, x/tests/a
adr/0*adr/0007-queuesadr/0007/notes, adr/1001
AGENTSAGENTSx/AGENTS
OpsRight needed on the path
read_concept, read_concepts, read_assetread
list_folder, read_index, read_log, search_concepts, query, get_schema, get_collection, check_relationsa rule on the bundle; paths it may not read are left out, not refused
create_concept, create_concepts, create_assetcreate
update_concept, update_concepts, update_asset, add_relation, remove_relationupdate (on the from concept)
delete_concept, delete_concepts, delete_assetdelete
move_concept, move_conceptsread and delete on from, create on to

A delegation never gets a managing op: no bundles created, renamed, deleted or exported, no relation types, collections or views, no shares with people, no keys, trusts or delegations. Listings (list_folder, search, index, log, query) leave out the paths it may not read instead of refusing. A refused write answers restricted and names what it may do instead; get_rights asks in advance. Questions: support@korpus.cloud.