Skip to content

API overview

The perms.io API has two services:

Service Proto What it does
Permissions permio.permissions.v1.PermissionsService Permissions, roles, grants and checks. This is the service your application calls.
Organisations permio.organisation.v1.OrganisationService Organisations, projects, API keys and team members. Mostly used by the dashboard.

Both are defined in Permio's protobuf definitions. The same host serves them as REST/JSON, gRPC and gRPC-web.

API hosts

Environment Base URL
Production https://temp-api.perms.io
Development https://dev-api.perms.io

Production will move to https://api.perms.io. The examples on this site read the host from a PERMIO_API shell variable, so you only need to change it in one place:

export PERMIO_API=https://temp-api.perms.io
export PERMIO_API_KEY=perms_...   # your API key

Protocols

Protocol How to call it
REST/JSON The HTTP method and path listed for each RPC in this reference, for example POST $PERMIO_API/permissions-service/v1/permissions/check. Request bodies accept the proto field names (project_name). Responses use camelCase JSON names (missingPermissions) and leave out fields that have default values.
gRPC Connect to the host on port 443 with TLS, for example temp-api.perms.io:443, and send the key as authorization metadata.
gRPC-web The protocol the TypeScript SDK uses. It works from servers, edge runtimes and browsers.

For GET and DELETE requests over REST, fields that aren't in the path go in the query string. Nested fields use dot notation, for example ?project_name=default&pagination.limit=50.

Authentication

Every request needs an API key, sent as a bearer token:

Authorization: Bearer perms_...

Keys are 44 characters long: perms_, 32 random base62 characters and a 6-character checksum. A key is shown once, when you create it in the dashboard. Only a hash of it is stored, and you can invalidate it at any time. Older JWT API keys are still accepted.

A key acts within its own project. Your organisation is worked out from the key, so permission requests don't include an organisation ID. A key gets its own grants on its project: by default check access, which allows Check, the access queries and the read-only calls. See Access levels.

Errors

A denied check is not an error. Check returns success with passed: false and a list of missing_permissions. Errors mean that the request itself could not be carried out:

gRPC status HTTP Meaning
INVALID_ARGUMENT (3) 400 Malformed input: an invalid resource URI, an empty permissions list, a bad project name, more than 100 items in a check, and so on.
UNAUTHENTICATED (16) 401 The API key is missing, malformed, unknown or invalidated.
PERMISSION_DENIED (7) 403 The API key isn't allowed to make this call. For example, a key without permission.check calling Check, or a check-only key creating a role. The message lists what is missing.
NOT_FOUND (5) 404 The permission, role, group or other entity doesn't exist. Also returned when a group:<slug> principal names a group that doesn't exist.
ALREADY_EXISTS (6) 409 A group with this name already exists in the project.
INTERNAL (13) 500 Something went wrong on our side.
UNAVAILABLE (14) 503 The service is temporarily unavailable. Safe to retry for reads.

Over REST, the error body carries the gRPC code and a message:

{"code": 16, "message": "invalid api key"}

Over gRPC and gRPC-web, the code arrives as grpc-status and the text as grpc-message.

Clients

The TypeScript SDK (npm install permio) is the recommended client. Besides the check helpers, it exposes every RPC on this page through permio.permissions and permio.organisations.

Every RPC has a REST path, listed with each method in this reference.

curl -X POST "$PERMIO_API/permissions-service/v1/permissions/check" \
  -H "Authorization: Bearer $PERMIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"project_name": "default", "principal_id": "alice", "resource_uris": ["/documents/42"], "permissions": ["document.read"]}'

Generated gRPC client. It isn't publicly available yet.

import (
    permissionsv1 "github.com/PrivateJAR/permio-go/proto/permissions/v1"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"
)

conn, err := grpc.NewClient("temp-api.perms.io:443",
    grpc.WithTransportCredentials(credentials.NewTLS(nil)))
// handle err
client := permissionsv1.NewPermissionsServiceClient(conn)
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+apiKey)

res, err := client.Check(ctx, &permissionsv1.CheckPermissionRequest{
    ProjectName:  "default",
    PrincipalId:  "alice",
    ResourceUris: []string{"/documents/42"},
    Permissions:  []string{"document.read"},
})

Packages: github.com/PrivateJAR/permio-go/proto/permissions/v1, .../proto/organisation/v1, .../proto/pagination/v1.

Generated gRPC stubs. They aren't publicly available yet.

import grpc
from permio.permissions.v1 import permissions_pb2, permissions_pb2_grpc

channel = grpc.secure_channel("temp-api.perms.io:443", grpc.ssl_channel_credentials())
client = permissions_pb2_grpc.PermissionsServiceStub(channel)

res = client.Check(
    permissions_pb2.CheckPermissionRequest(
        project_name="default",
        principal_id="alice",
        resource_uris=["/documents/42"],
        permissions=["document.read"],
    ),
    metadata=[("authorization", f"Bearer {api_key}")],
)
print(res.passed)

Modules: permio.permissions.v1, permio.organisation.v1, permio.pagination.v1.

Generated prost/tonic client. It isn't publicly available yet.

use permio_rust::permissions::v1::permissions_service_client::PermissionsServiceClient;
use permio_rust::permissions::v1::CheckPermissionRequest;

Send the key as authorization: Bearer <key> metadata, for example with a tonic interceptor.

Field names in this reference are the proto names (project_name). The generated clients use their language's convention: ProjectName in Go, project_name in Python and Rust, and projectName in TypeScript.