Granite API (1.0.0)

Download OpenAPI specification:

License: LicenseRef-Proprietary

REST API for Granite clients, bots, and integrations.

Conventions (apply to every operation; see PLAN.md 3.2 and 3.6):

  • Base URL https://api.granite.md. All public operations live under /v1. After Phase 6 ships, /v1 changes are additive only.
  • Errors use RFC 9457 problem details (application/problem+json, schema Problem). The type URI is https://granite.md/problems/<slug>; code refines it for programmatic handling. Every response carries X-Request-ID, also echoed as request_id in problems.
  • Pagination is cursor based. List operations accept cursor and limit and return { "items": [...], "next_cursor": "..." }. next_cursor is absent on the last page. Cursors are opaque; clients must not parse or build them.
  • Idempotency: non-idempotent POST operations accept an Idempotency-Key header (1-255 printable ASCII). Repeating a request with the same key and body within 24 hours returns the original response; the same key with a different body returns 409 with code idempotency_key_reused.
  • IDs are UUIDv7 strings. Timestamps are RFC 3339 in UTC.
  • Authentication: Authorization: Bearer <token> with a session access token, a personal API token (gr_pat_...), or an OAuth access token. Scopes are listed per operation.
  • Rate limits: each personal API token may make 600 requests a minute. Over the limit the API answers 429 with code token_rate_limited and a Retry-After header (seconds).

meta

Service information.

Service information

Returns the API and server versions. Does not require authentication.

Responses

Response samples

Content type
application/json
{
  • "api_version": "1.0.0",
  • "server_version": "2026.10.1"
}

Report an app error

Crash and error reports from the apps (PLAN.md P5-T01), written to the server log for the on-call person. No sign-in is needed (an app can fail before it). Reports must not contain note text, file names, or anything the person typed. Limited per address.

Request Body schema: application/json
required
kind
required
string
Enum: "crash" "error" "render"
message
required
string <= 1000 characters
stack
string <= 8000 characters
screen
string <= 200 characters
platform
required
string
Enum: "web" "macos" "windows" "linux" "ios" "android" "cli" "other"
app_version
required
string <= 50 characters
os_version
string <= 50 characters

Responses

Request samples

Content type
application/json
{
  • "kind": "crash",
  • "message": "string",
  • "stack": "string",
  • "screen": "string",
  • "platform": "web",
  • "app_version": "string",
  • "os_version": "string"
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

auth

Sign-up, login, sessions, and password reset.

Create an account

Creates an unverified account and emails a verification link. The response is the same whether or not the email is already registered (the existing owner is notified).

Request Body schema: application/json
required
email
required
string <email> <= 254 characters
password
required
string [ 10 .. 256 ] characters
name
string <= 200 characters

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "password": "stringstri",
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "status": "accepted"
}

Confirm an email address

Request Body schema: application/json
required
token
required
string <= 200 characters

Responses

Request samples

Content type
application/json
{
  • "token": "string"
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Send a new verification email

Always answers 202, whether or not the email has an unverified account.

Request Body schema: application/json
required
email
required
string <email> <= 254 characters

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com"
}

Response samples

Content type
application/json
{
  • "status": "accepted"
}

Sign in

Checks the password and starts a session for the device. After repeated failures the email is locked for a while (429), whether or not it has an account.

Request Body schema: application/json
required
email
required
string <email> <= 254 characters
password
required
string <= 256 characters
required
object (DeviceInfo)

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "password": "string",
  • "device": {
    }
}

Response samples

Content type
application/json
{
  • "access_token": "string",
  • "token_type": "Bearer",
  • "expires_in": 0,
  • "refresh_token": "string",
  • "refresh_expires_in": 0,
  • "device_id": "3bafab7b-4400-4bcf-8e6e-09f954699940",
  • "user": {
    }
}

Start single sign-on

Finds the workspace that verified the email's domain and set up SSO, and returns the identity provider's sign-in URL. Open it in the browser; the provider returns to the web app's /sso/callback, which finishes with POST /v1/auth/sso/finish.

Request Body schema: application/json
required
email
required
string <= 320 characters

Responses

Request samples

Content type
application/json
{
  • "email": "string"
}

Response samples

Content type
application/json
{
  • "authorize_url": "string"
}

Finish single sign-on

Exchanges the code from the identity provider and starts a session. People new to the workspace join it with the connection's role. Two-step verification is left to the identity provider.

Request Body schema: application/json
required
state
string <= 200 characters
code
string <= 4000 characters
handoff
string <= 200 characters
required
object (DeviceInfo)

Responses

Request samples

Content type
application/json
{
  • "state": "string",
  • "code": "string",
  • "handoff": "string",
  • "device": {
    }
}

Response samples

Content type
application/json
{
  • "access_token": "string",
  • "token_type": "Bearer",
  • "expires_in": 0,
  • "refresh_token": "string",
  • "refresh_expires_in": 0,
  • "device_id": "3bafab7b-4400-4bcf-8e6e-09f954699940",
  • "user": {
    }
}

Get new tokens

Exchanges a refresh token for a new access token and a new refresh token. Each refresh token works once; presenting a used one ends every session of that sign-in (code refresh_token_reused). Clients must not refresh concurrently.

Request Body schema: application/json
required
refresh_token
required
string <= 200 characters

Responses

Request samples

Content type
application/json
{
  • "refresh_token": "string"
}

Response samples

Content type
application/json
{
  • "access_token": "string",
  • "token_type": "Bearer",
  • "expires_in": 0,
  • "refresh_token": "string",
  • "refresh_expires_in": 0,
  • "device_id": "3bafab7b-4400-4bcf-8e6e-09f954699940",
  • "user": {
    }
}

Sign out this device

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Email a password reset link

Always answers 202, whether or not the email has an account.

Request Body schema: application/json
required
email
required
string <email> <= 254 characters

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com"
}

Response samples

Content type
application/json
{
  • "status": "accepted"
}

Set a new password

Sets the password from a reset link and signs out every device.

Request Body schema: application/json
required
token
required
string <= 200 characters
password
required
string [ 10 .. 256 ] characters

Responses

Request samples

Content type
application/json
{
  • "token": "string",
  • "password": "stringstri"
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Finish signing in with a second factor

Send either code (from the authenticator app) or recovery_code. Each works once.

Request Body schema: application/json
required
mfa_token
required
string <= 2000 characters
code
string <= 10 characters
recovery_code
string <= 40 characters

Responses

Request samples

Content type
application/json
{
  • "mfa_token": "string",
  • "code": "string",
  • "recovery_code": "string"
}

Response samples

Content type
application/json
{
  • "access_token": "string",
  • "token_type": "Bearer",
  • "expires_in": 0,
  • "refresh_token": "string",
  • "refresh_expires_in": 0,
  • "device_id": "3bafab7b-4400-4bcf-8e6e-09f954699940",
  • "user": {
    }
}

Confirm the password for sensitive actions

Re-checks the password (and the authenticator code when two-factor authentication is on). For the next 10 minutes this session may change two-factor settings. Other sensitive actions answer 403 with code step_up_required until this is done.

Authorizations:
bearerAuth
Request Body schema: application/json
required
password
required
string <= 256 characters
code
string <= 10 characters

Authenticator code, required when two-factor authentication is on.

Responses

Request samples

Content type
application/json
{
  • "password": "string",
  • "code": "string"
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Sign in with Google or Apple

Send the ID token from the provider's sign-in SDK. Before signing in, the client makes a random nonce, passes hex(SHA-256(nonce)) to the provider, and sends the raw nonce here. A first sign-in with a verified email links to the account with that email or creates one. Returns 202 when the account has two-factor authentication on.

path Parameters
provider
required
string
Enum: "google" "apple"
Request Body schema: application/json
required
id_token
required
string <= 8000 characters
nonce
required
string [ 16 .. 200 ] characters
name
string <= 200 characters

Apple gives the user's name to the app only on the first sign-in.

required
object (DeviceInfo)

Responses

Request samples

Content type
application/json
{
  • "id_token": "string",
  • "nonce": "stringstringstri",
  • "name": "string",
  • "device": {
    }
}

Response samples

Content type
application/json
{
  • "access_token": "string",
  • "token_type": "Bearer",
  • "expires_in": 0,
  • "refresh_token": "string",
  • "refresh_expires_in": 0,
  • "device_id": "3bafab7b-4400-4bcf-8e6e-09f954699940",
  • "user": {
    }
}

me

The signed-in user.

The signed-in user

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "email": "user@example.com",
  • "name": "string",
  • "email_verified": true,
  • "two_factor_enabled": true,
  • "created_at": "2019-08-24T14:15:22Z"
}

Change your profile

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string <= 200 characters

Responses

Request samples

Content type
application/json
{
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "email": "user@example.com",
  • "name": "string",
  • "email_verified": true,
  • "two_factor_enabled": true,
  • "created_at": "2019-08-24T14:15:22Z"
}

Delete the account

Needs a recent password check (POST /v1/auth/step-up). The account is anonymized at once and signed out everywhere; its personal vaults are purged within the hour, then the account itself. Workspaces you own alone are deleted too; owning one with other members is refused (workspace_owner): transfer it or delete it first. Encrypted backups age out within 35 days.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Download everything in the account

A zip with account.json (profile, devices, API tokens, workspaces) and each personal vault as a folder of plain files under Vaults/.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Change your password

Requires a recent step-up (POST /v1/auth/step-up). Signs out every other device.

Authorizations:
bearerAuth
Request Body schema: application/json
required
password
required
string [ 10 .. 256 ] characters

Responses

Request samples

Content type
application/json
{
  • "password": "stringstri"
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Your signed-in devices

Devices that have not been signed out, most recently used first. The plan limits how many may be signed in at once; signing in on another one answers 403 with code device_limit_reached until a device is signed out here.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Sign out every other device

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Rename a device

Authorizations:
bearerAuth
path Parameters
device_id
required
string <uuid>
Request Body schema: application/json
required
name
required
string [ 1 .. 100 ] characters

Responses

Request samples

Content type
application/json
{
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "platform": "web",
  • "app_version": "string",
  • "last_seen_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z",
  • "active_sessions": 0,
  • "current": true
}

Sign a device out

Its sessions end; its access tokens stop working within 30 seconds.

Authorizations:
bearerAuth
path Parameters
device_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Your personal API tokens

Tokens that are not revoked. Token values are never shown again.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Create a personal API token

Requires a signed-in session with a recent step-up (POST /v1/auth/step-up); tokens cannot create tokens. The value (gr_pat_ + 36 characters, the last 6 a checksum) is returned only in this response. The plan limits how many tokens may be active (403 plan_limit_api_tokens). Send it as Authorization: Bearer <token>.

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string [ 1 .. 100 ] characters
scopes
required
Array of strings (APITokenScope) non-empty
Items Enum: "vaults:read" "vaults:write" "sync" "kb:query"
expires_in_days
integer [ 1 .. 3650 ]

Omitted means the token does not expire.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "scopes": [
    ],
  • "expires_in_days": 1
}

Response samples

Content type
application/json
{
  • "token": {
    },
  • "value": "string"
}

Revoke a personal API token

The token stops working at once.

Authorizations:
bearerAuth
path Parameters
token_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Plan, limits, and usage

The single source of plan limits for clients (PLAN.md 1.2). Null means unlimited.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "plan": {
    },
  • "limits": {
    },
  • "usage": {
    }
}

Feature flags

Which features are turned on for the signed-in user (PLAN.md Appendix G.2). Apps read this after sign-in and hide features whose flag is off or missing. A flag stays on or off for a user while its rollout grows.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "flags": {
    }
}

Turn off two-factor authentication

Requires a recent step-up.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Start authenticator app setup

Returns a new secret to show as a QR code. Requires a recent step-up.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "secret": "string",
  • "otpauth_uri": "string"
}

Finish authenticator app setup

Turns two-factor authentication on and returns recovery codes, shown only once.

Authorizations:
bearerAuth
Request Body schema: application/json
required
code
required
string <= 10 characters

Responses

Request samples

Content type
application/json
{
  • "code": "string"
}

Response samples

Content type
application/json
{
  • "recovery_codes": [
    ]
}

Replace recovery codes

Old codes stop working. Requires a recent step-up.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "recovery_codes": [
    ]
}

Receive push notifications on this device

Saves this device's APNs or FCM token (the mobile apps, PLAN.md P5-T06). The device gets alerts for mentions and shares, and silent "sync now" nudges when a vault it can use changes elsewhere. Only a signed-in app can do this, not an API token: 403. A token moves to the device that saved it last.

Authorizations:
bearerAuth
Request Body schema: application/json
required
provider
required
string
Enum: "apns" "fcm"

apns on iPhone and iPad, fcm on Android.

token
required
string [ 1 .. 4096 ] characters
sandbox
boolean
Default: false

An APNs token from a development build (Apple's sandbox gateway).

Responses

Request samples

Content type
application/json
{
  • "provider": "apns",
  • "token": "string",
  • "sandbox": false
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Stop push notifications on this device

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

vaults

Vaults (folders of notes that sync).

List vaults

Authorizations:
bearerAuth
query Parameters
cursor
string <= 512 characters

Opaque cursor from a previous page's next_cursor.

limit
integer <int32> [ 1 .. 200 ]
Default: 50

Maximum items to return.

trash
boolean
Default: false

List deleted vaults that can still be restored instead of active ones.

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "next_cursor": "string"
}

Create a vault

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string [ 1 .. 100 ] characters
workspace_id
string <uuid>

Create a shared vault in this workspace (members and above) instead of a personal one.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "deleted_at": "2019-08-24T14:15:22Z",
  • "purge_after": "2019-08-24T14:15:22Z"
}

Get a vault

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "deleted_at": "2019-08-24T14:15:22Z",
  • "purge_after": "2019-08-24T14:15:22Z"
}

Rename a vault

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
Request Body schema: application/json
required
name
required
string [ 1 .. 100 ] characters

Responses

Request samples

Content type
application/json
{
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "deleted_at": "2019-08-24T14:15:22Z",
  • "purge_after": "2019-08-24T14:15:22Z"
}

Move a vault to the trash

The vault can be restored for 30 days (purge_after), then it is deleted permanently.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "deleted_at": "2019-08-24T14:15:22Z",
  • "purge_after": "2019-08-24T14:15:22Z"
}

Download a vault as a zip of plain files

Every file you can read, at its path, exactly as synced (PLAN.md 5, Portability).

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Restore a vault from the trash

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "deleted_at": "2019-08-24T14:15:22Z",
  • "purge_after": "2019-08-24T14:15:22Z"
}

Who a shared vault is shared with

Vault admins.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Share a vault or a folder

Vault admins of workspace vaults. With folder, access applies to that folder only. Granting the same person or group and folder again changes the access. Guests can only read and comment.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
Request Body schema: application/json
required
principal_type
required
string
Enum: "user" "group"
principal_id
required
string <uuid>
access
required
string
Enum: "read" "comment" "write" "admin"
folder
string <= 1024 characters

Responses

Request samples

Content type
application/json
{
  • "principal_type": "user",
  • "principal_id": "b0fe3bdd-8201-445a-aab3-a018692d6aca",
  • "access": "read",
  • "folder": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "principal_type": "user",
  • "principal_id": "b0fe3bdd-8201-445a-aab3-a018692d6aca",
  • "access": "read",
  • "folder": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Stop sharing

Vault admins. Applies from the next request.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
grant_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

sync

The sync protocol (PLAN.md Appendix B).

Snapshot of the vault's files

Live files as of at (default: the current head). Pass the same at with cursor to get later pages of the same snapshot (Appendix B.3).

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
query Parameters
at
integer <int64> >= 0
cursor
string <= 512 characters

Opaque cursor from a previous page's next_cursor.

limit
integer <int32> [ 1 .. 200 ]
Default: 50

Maximum items to return.

Responses

Response samples

Content type
application/json
{
  • "head_seq": 0,
  • "files": [
    ],
  • "next_cursor": "string"
}

Changes after a sequence number

Answers 410 with code cursor_expired when since is older than the kept log.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
query Parameters
since
required
integer <int64> >= 0
limit
integer [ 1 .. 1000 ]
Default: 500

Responses

Response samples

Content type
application/json
{
  • "changes": [
    ],
  • "head_seq": 0,
  • "has_more": true
}

Apply a batch of changes atomically

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
header Parameters
Idempotency-Key
string [ 1 .. 255 ] characters ^[\x21-\x7E]+$

Makes a non-idempotent POST safe to retry. See the conventions above.

Request Body schema: application/json
required
device_id
string <uuid>
required
Array of objects (CommitOp) <= 1000 items

Responses

Request samples

Content type
application/json
{
  • "device_id": "3bafab7b-4400-4bcf-8e6e-09f954699940",
  • "ops": [
    ]
}

Response samples

Content type
application/json
{
  • "head_seq": 0,
  • "results": [
    ]
}

Which blobs still need uploading

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
Request Body schema: application/json
required
hashes
required
Array of strings <= 1000 items [ items^[0-9a-f]{64}$ ]

Responses

Request samples

Content type
application/json
{
  • "hashes": [
    ]
}

Response samples

Content type
application/json
{
  • "missing": [
    ]
}

Upload a blob (up to 8 MiB)

The server checks the bytes against the hash and the plan's limits. Larger blobs use upload sessions.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
hash
required
string^[0-9a-f]{64}$
Request Body schema: application/octet-stream
required
string <binary>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Download a blob

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
hash
required
string^[0-9a-f]{64}$
header Parameters
Range
string <= 100 characters

One range, bytes=start-end or bytes=start-.

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Start a resumable upload

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
Request Body schema: application/json
required
hash
required
string^[0-9a-f]{64}$
size
required
integer <int64> >= 0

Responses

Request samples

Content type
application/json
{
  • "hash": "string",
  • "size": 0
}

Response samples

Content type
application/json
{
  • "upload_id": "string",
  • "hash": "string",
  • "size": 0,
  • "received": 0
}

How much of an upload arrived

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
upload_id
required
string^[0-9a-f]{32}$

Responses

Response samples

Content type
application/json
{
  • "upload_id": "string",
  • "hash": "string",
  • "size": 0,
  • "received": 0
}

Send the next part (up to 16 MiB)

Upload-Offset must equal the bytes received so far (code upload_offset_mismatch otherwise).

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
upload_id
required
string^[0-9a-f]{32}$
header Parameters
Upload-Offset
required
integer <int64> >= 0
Request Body schema: application/octet-stream
required
string <binary>

Responses

Response samples

Content type
application/json
{
  • "upload_id": "string",
  • "hash": "string",
  • "size": 0,
  • "received": 0
}

Finish an upload

Checks the bytes against the hash and stores the blob.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
upload_id
required
string^[0-9a-f]{32}$

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

A file's version history, newest first

Superseded versions are kept for the plan's history period (PLAN.md 1.2).

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>
query Parameters
before
integer <int64> >= 1

Return versions with a lower seq (from next_before of the previous page).

limit
integer <int32> [ 1 .. 200 ]
Default: 50

Maximum items to return.

Responses

Response samples

Content type
application/json
{
  • "versions": [
    ],
  • "next_before": 0
}

One version

Download its content with GET /v1/vaults/{vault_id}/blobs/{blob_hash}.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>
version_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
  • "seq": 0,
  • "op": "put",
  • "path": "string",
  • "blob_hash": "string",
  • "size": 0,
  • "content_type": "string",
  • "deleted": true,
  • "author_id": "78424c75-5c41-4b25-9735-3c9f7d05c59e",
  • "device_id": "3bafab7b-4400-4bcf-8e6e-09f954699940",
  • "created_at": "2019-08-24T14:15:22Z"
}

Make an old version current again

Commits a new version with the old content at the file's current path (a deleted file comes back).

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>
version_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "head_seq": 0,
  • "results": [
    ]
}

notes

Read and change single notes by their text, for scripts and integrations. Each change is an ordinary commit that synced devices, history, and search see.

List notes

Live files in path order (all kinds; GET on a non-text file answers not_a_note). folder limits the list to one folder and its subfolders. Needs vaults:read.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
query Parameters
folder
string <= 1024 characters
cursor
string <= 512 characters

Opaque cursor from a previous page's next_cursor.

limit
integer <int32> [ 1 .. 200 ]
Default: 50

Maximum items to return.

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "next_cursor": "string"
}

Create a note

Creates a text file (.md, .txt, …) of at most 5 MB. A live file at the same path (compared without case) answers 409 path_taken. Needs vaults:write or sync.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
Request Body schema: application/json
required
path
required
string [ 1 .. 1024 ] characters
content
required
string

Responses

Request samples

Content type
application/json
{
  • "path": "string",
  • "content": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "path": "string",
  • "version_id": "9e94c502-ca41-4342-a7f7-af96b444512c",
  • "size": 0,
  • "updated_at": "2019-08-24T14:15:22Z",
  • "content": "string"
}

Read a note by its path

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
query Parameters
path
required
string [ 1 .. 1024 ] characters

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "path": "string",
  • "version_id": "9e94c502-ca41-4342-a7f7-af96b444512c",
  • "size": 0,
  • "updated_at": "2019-08-24T14:15:22Z",
  • "content": "string"
}

Read a note

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "path": "string",
  • "version_id": "9e94c502-ca41-4342-a7f7-af96b444512c",
  • "size": 0,
  • "updated_at": "2019-08-24T14:15:22Z",
  • "content": "string"
}

Replace a note's text

With base_version_id, answers 409 version_mismatch if the note changed since that version; without it, the last write wins.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>
Request Body schema: application/json
required
content
required
string
base_version_id
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "content": "string",
  • "base_version_id": "330601a7-a4c3-4e92-beac-4d56d32a4b5d"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "path": "string",
  • "version_id": "9e94c502-ca41-4342-a7f7-af96b444512c",
  • "size": 0,
  • "updated_at": "2019-08-24T14:15:22Z",
  • "content": "string"
}

Edit part of a note

Applies find/replace (the first match, or every match with all), then prepend and append. Without base_version_id the edit is applied to the newest text, so concurrent edits from synced devices are kept. A missing find answers 400 find_not_found.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>
Request Body schema: application/json
required
find
string non-empty
replace
string
all
boolean
Default: false
prepend
string
append
string
base_version_id
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "find": "string",
  • "replace": "string",
  • "all": false,
  • "prepend": "string",
  • "append": "string",
  • "base_version_id": "330601a7-a4c3-4e92-beac-4d56d32a4b5d"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "path": "string",
  • "version_id": "9e94c502-ca41-4342-a7f7-af96b444512c",
  • "size": 0,
  • "updated_at": "2019-08-24T14:15:22Z",
  • "content": "string"
}

Delete a note

The note stays in version history and can be restored from there.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>
query Parameters
base_version_id
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Rename or move a note

Links to the note are not rewritten. A taken path answers 409 path_taken.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>
Request Body schema: application/json
required
path
required
string [ 1 .. 1024 ] characters
base_version_id
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "path": "string",
  • "base_version_id": "330601a7-a4c3-4e92-beac-4d56d32a4b5d"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "path": "string",
  • "version_id": "9e94c502-ca41-4342-a7f7-af96b444512c",
  • "size": 0,
  • "updated_at": "2019-08-24T14:15:22Z",
  • "content": "string"
}

webhooks

Signed HTTP callbacks for vault events (Business plan). Granite posts a JSON WebhookEvent to your URL within seconds of each change. Answer with any 2xx status within 10 seconds; other answers and timeouts are retried after 1 min, 5 min, 30 min, 2 h, 6 h, and 24 h, then the delivery is marked failed (410 Gone stops retries at once). Every attempt is in the delivery log and any delivery can be replayed. Delivery is at least once: skip event ids you have already handled.

Events: note.created, note.updated, note.moved, note.deleted; vault.shared and member.added/member.removed/member.role_changed start with sharing. Subscribe with exact names, note.*, member.*, or *.

Headers: Granite-Event, Granite-Delivery, Granite-Webhook, and Granite-Signature: t=<unix seconds>,v1=<signature>, where the signature is the hex HMAC-SHA256 of <t>.<raw body> keyed with the webhook secret (whsec_...). Check it against the raw body before parsing, and reject timestamps more than 5 minutes old.

Node.js:

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const want = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  return timingSafeEqual(Buffer.from(want), Buffer.from(parts.v1 ?? ""));
}

Python:

import hashlib, hmac, time

def verify(secret: str, header: str, raw_body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    want = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, parts.get("v1", ""))

Go:

func verify(secret, header string, body []byte) bool {
    var t, sig string
    for _, p := range strings.Split(header, ",") {
        k, v, _ := strings.Cut(p, "=")
        if k == "t" { t = v } else if k == "v1" { sig = v }
    }
    ts, err := strconv.ParseInt(t, 10, 64)
    if err != nil || math.Abs(time.Since(time.Unix(ts, 0)).Seconds()) > 300 {
        return false
    }
    m := hmac.New(sha256.New, []byte(secret))
    m.Write([]byte(t + "."))
    m.Write(body)
    return hmac.Equal([]byte(hex.EncodeToString(m.Sum(nil))), []byte(sig))
}

List a vault's webhooks

Needs administrator access to the vault.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Add a webhook

Business plan (403 plan_limit_webhooks otherwise); at most 10 per vault. Events from now on are delivered. The secret is returned only here and when rotated.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
Request Body schema: application/json
required
url
required
string <= 2000 characters

An https URL on the public internet.

description
string <= 200 characters
events
required
Array of strings (WebhookEventType) non-empty
Items Enum: "*" "note.*" "member.*" "note.created" "note.updated" "note.moved" "note.deleted" "vault.shared" "member.added" "member.removed" "member.role_changed"

Responses

Request samples

Content type
application/json
{
  • "url": "string",
  • "description": "string",
  • "events": [
    ]
}

Response samples

Content type
application/json
{
  • "webhook": {
    },
  • "secret": "string"
}

Get a webhook

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
webhook_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "vault_id": "432b199b-1f71-42bf-ba0b-33d512afa9de",
  • "url": "string",
  • "description": "string",
  • "events": [
    ],
  • "active": true,
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Change a webhook

Omitted fields keep their value. rotate_secret returns a new secret once.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
webhook_id
required
string <uuid>
Request Body schema: application/json
required
url
string <= 2000 characters
description
string <= 200 characters
events
Array of strings (WebhookEventType) non-empty
Items Enum: "*" "note.*" "member.*" "note.created" "note.updated" "note.moved" "note.deleted" "vault.shared" "member.added" "member.removed" "member.role_changed"
active
boolean
rotate_secret
boolean

Responses

Request samples

Content type
application/json
{
  • "url": "string",
  • "description": "string",
  • "events": [
    ],
  • "active": true,
  • "rotate_secret": true
}

Response samples

Content type
application/json
{
  • "webhook": {
    },
  • "secret": "string"
}

Delete a webhook

Its pending deliveries and delivery log are deleted too.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
webhook_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

The delivery log

Deliveries, newest first, with the outcome of their latest attempt.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
webhook_id
required
string <uuid>
query Parameters
cursor
string <= 512 characters

Opaque cursor from a previous page's next_cursor.

limit
integer <int32> [ 1 .. 200 ]
Default: 50

Maximum items to return.

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "next_cursor": "string"
}

Send a delivery again

Creates a new delivery of the same event (same event id), sent right away.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
webhook_id
required
string <uuid>
delivery_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "event_id": "a7a26ff2-e851-45b6-9634-d595f45458b7",
  • "event_type": "string",
  • "state": "pending",
  • "attempts": 0,
  • "next_attempt_at": "2019-08-24T14:15:22Z",
  • "last_status": 0,
  • "last_error": "string",
  • "replay_of": "eb051ed0-925f-449a-a6c5-0c118d499f91",
  • "created_at": "2019-08-24T14:15:22Z",
  • "delivered_at": "2019-08-24T14:15:22Z",
  • "payload": {
    }
}

oauth

The consent page and connected apps of Granite's OAuth 2.1 server. The protocol endpoints themselves (/.well-known/oauth-authorization-server, /oauth/register, /oauth/authorize, /oauth/token, /oauth/revoke) follow the OAuth RFCs and are not part of this API.

Check an app's authorization request

For the consent page: validates the request an app sent to /oauth/authorize and describes the app. When the request is invalid but its redirect URI is the app's, error_redirect is where to send the browser.

Authorizations:
bearerAuth
query Parameters
client_id
required
string <= 200 characters
redirect_uri
string <= 2000 characters
response_type
string <= 50 characters
scope
string <= 500 characters
state
string <= 2000 characters
code_challenge
string <= 200 characters
code_challenge_method
string <= 20 characters
resource
string <= 2000 characters

Responses

Response samples

Content type
application/json
{
  • "client_id": "string",
  • "client_name": "string",
  • "client_uri": "string",
  • "logo_uri": "string",
  • "redirect_host": "string",
  • "scopes": [
    ],
  • "error_redirect": "string"
}

Allow or deny an app

Records the decision. Allowing creates (or updates) the app's grant with the chosen permissions and vaults (empty vault_ids means all vaults). The browser goes to redirect_to either way.

Authorizations:
bearerAuth
Request Body schema: application/json
required
required
object (OAuthAuthorizeRequest)
approve
required
boolean
scopes
Array of strings (OAuthScope)
Items Enum: "vaults:read" "vaults:write" "kb:query"
vault_ids
Array of strings <uuid> <= 100 items [ items <uuid > ]

Responses

Request samples

Content type
application/json
{
  • "request": {
    },
  • "approve": true,
  • "scopes": [
    ],
  • "vault_ids": [
    ]
}

Response samples

Content type
application/json
{
  • "redirect_to": "string"
}

Apps you allowed to use your vaults

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Disconnect an app

Its access ends at once; it has to ask again.

Authorizations:
bearerAuth
path Parameters
grant_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

workspaces

Company workspaces (Business plan): members with roles (owner, admin, member, guest), invitations by email or link, and verified email domains that let colleagues join. Anyone who is not a member gets 404 for everything about a workspace.

Your workspaces

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Create a workspace

You become its owner. Needs the Business plan (403 plan_limit_workspaces).

Authorizations:
bearerAuth
Request Body schema: application/json
required
name
required
string [ 1 .. 100 ] characters
slug
string^[a-z0-9][a-z0-9-]{1,47}$

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "slug": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "slug": "string",
  • "role": "owner",
  • "domain_capture": true,
  • "policy_block": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Get a workspace

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "slug": "string",
  • "role": "owner",
  • "domain_capture": true,
  • "policy_block": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Change a workspace

Admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
Request Body schema: application/json
required
name
string [ 1 .. 100 ] characters
domain_capture
boolean

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "domain_capture": true
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "slug": "string",
  • "role": "owner",
  • "domain_capture": true,
  • "policy_block": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Delete a workspace

The owner. Its vaults and keys are destroyed after the recovery window.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Members

Members and above.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Change a member's role

Admins manage members and guests; only the owner manages admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
user_id
required
string <uuid>
Request Body schema: application/json
required
role
required
string
Enum: "admin" "member" "guest"

Responses

Request samples

Content type
application/json
{
  • "role": "admin"
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Remove a member

Admins (only the owner removes admins). Removing yourself is leaving.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
user_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Transfer ownership

The owner; they become an admin.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
Request Body schema: application/json
required
user_id
required
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Leave a workspace

The owner must transfer ownership first.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Join through your email domain

For workspaces in GET /v1/me/joinable-workspaces.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Pending invitations

Admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Invite someone

Admins. With email, sends an invitation for that address (single use, 7 days); without, returns a link anyone signed in can use until it expires (30 days) or is revoked. Only the owner invites admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
Request Body schema: application/json
required
email
string <= 254 characters
role
required
string
Enum: "admin" "member" "guest"

Responses

Request samples

Content type
application/json
{
  • "email": "string",
  • "role": "admin"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "email": "string",
  • "role": "admin",
  • "expires_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z",
  • "link": "string"
}

Revoke an invitation

Admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
invitation_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Claimed email domains

Admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Claim an email domain

Admins. Add the returned TXT record to the domain's DNS, then verify.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
Request Body schema: application/json
required
domain
required
string <= 253 characters

Responses

Request samples

Content type
application/json
{
  • "domain": "string"
}

Response samples

Content type
application/json
{
  • "domain": "string",
  • "txt_record": "string",
  • "verified_at": "2019-08-24T14:15:22Z"
}

Drop a claimed domain

Admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
domain
required
string <= 253 characters

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Verify a domain

Admins. Answers 409 domain_not_verified until the TXT record is visible.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
domain
required
string <= 253 characters

Responses

Response samples

Content type
application/json
{
  • "domain": "string",
  • "txt_record": "string",
  • "verified_at": "2019-08-24T14:15:22Z"
}

Workspaces you can join

Workspaces whose verified domain matches your verified email and that allow joining by domain.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

What an invitation is for

Does not need sign-in. The token is sent in the body so it stays out of logs.

Request Body schema: application/json
required
token
required
string <= 200 characters

Responses

Request samples

Content type
application/json
{
  • "token": "string"
}

Response samples

Content type
application/json
{
  • "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
  • "workspace_name": "string",
  • "role": "string",
  • "email": "string"
}

Accept an invitation

Email invitations work only for the invited, verified address.

Authorizations:
bearerAuth
Request Body schema: application/json
required
token
required
string <= 200 characters

Responses

Request samples

Content type
application/json
{
  • "token": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "slug": "string",
  • "role": "owner",
  • "domain_capture": true,
  • "policy_block": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Groups

Members and above.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Create a group

Admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
Request Body schema: application/json
required
name
required
string [ 1 .. 100 ] characters

Responses

Request samples

Content type
application/json
{
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "members": 0
}

Rename a group

Admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
group_id
required
string <uuid>
Request Body schema: application/json
required
name
required
string [ 1 .. 100 ] characters

Responses

Request samples

Content type
application/json
{
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "members": 0
}

Delete a group

Admins. Its grants stop applying.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
group_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

A group's members

Members and above.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
group_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Add a member to a group

Admins. Takes effect on the member's next request.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
group_id
required
string <uuid>
Request Body schema: application/json
required
user_id
required
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Remove a member from a group

Admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
group_id
required
string <uuid>
user_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

The workspace's audit log

Admins. Newest first. Filters combine.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
query Parameters
cursor
string <= 512 characters

Opaque cursor from a previous page's next_cursor.

limit
integer <int32> [ 1 .. 200 ]
Default: 50

Maximum items to return.

action
string <= 100 characters

Actions starting with this, such as member. or vault.shared

actor
string <uuid>
since
string <date-time>
until
string <date-time>

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "next_cursor": "string"
}

Download the audit log

Admins. CSV, or JSON with one event per line. Up to 100,000 events, newest first.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
query Parameters
format
required
string
Enum: "csv" "json"
action
string <= 100 characters
since
string <date-time>
until
string <date-time>

Responses

Response samples

Content type
No sample

The workspace's single sign-on connection

Admins. 404 when SSO is not set up.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "protocol": "oidc",
  • "issuer": "string",
  • "client_id": "string",
  • "saml": {
    },
  • "jit_role": "member",
  • "enforced": true,
  • "tested_at": "2019-08-24T14:15:22Z",
  • "redirect_url": "string"
}

Connect an identity provider (OpenID Connect or SAML 2.0)

Admins. For OpenID Connect, register redirect_url with the provider. For SAML, give the identity provider's metadata (idp_metadata XML or idp_metadata_url) and register Granite's service provider with it: saml.metadata_url serves Granite's metadata (GET /v1/sso/saml/{workspace_id}/metadata); the provider posts responses to saml.acs_url (POST /v1/sso/saml/{workspace_id}/acs). Responses must be signed (or carry signed assertions); replayed assertions are refused; sign-in can start at Granite or at the provider. People at the workspace's verified domains can then sign in with SSO. enforced needs one successful SSO sign-in with the current issuer and client first; then members at those domains (all but the owner) must use SSO, and their other sessions cannot open the workspace's vaults.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
Request Body schema: application/json
required
protocol
string
Default: "oidc"
Enum: "oidc" "saml"
issuer
string <= 500 characters

OpenID Connect.

client_id
string <= 500 characters
idp_metadata
string <= 1048576 characters

SAML identity provider metadata XML (leave out to keep the stored one).

idp_metadata_url
string <= 2000 characters

Where to fetch the SAML identity provider metadata instead.

client_secret
string <= 2000 characters

Required the first time; leave out to keep the stored secret.

jit_role
string
Enum: "member" "guest"
enforced
required
boolean

Responses

Request samples

Content type
application/json
{
  • "protocol": "oidc",
  • "issuer": "string",
  • "client_id": "string",
  • "idp_metadata": "string",
  • "idp_metadata_url": "string",
  • "client_secret": "string",
  • "jit_role": "member",
  • "enforced": true
}

Response samples

Content type
application/json
{
  • "protocol": "oidc",
  • "issuer": "string",
  • "client_id": "string",
  • "saml": {
    },
  • "jit_role": "member",
  • "enforced": true,
  • "tested_at": "2019-08-24T14:15:22Z",
  • "redirect_url": "string"
}

Remove single sign-on

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

The workspace's SCIM provisioning

Admins. SCIM 2.0 (RFC 7644) lives at base_url (/scim/v2): Users are the workspace's members (emails at verified domains), Groups its groups. Deactivating or deleting a user suspends or removes the membership and revokes the account's sessions, API tokens, and OAuth grants at once. The owner cannot be deprovisioned through SCIM.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "enabled": true,
  • "base_url": "string",
  • "token_prefix": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "last_used_at": "2019-08-24T14:15:22Z"
}

Create or replace the SCIM token

Admins. The token is shown only here; give it to the identity provider with base_url.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "token": "string",
  • "base_url": "string"
}

Turn SCIM provisioning off

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

The workspace's security policy

Admins.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "require_two_factor": true,
  • "session_hours": 0,
  • "clients": [
    ],
  • "mcp": "on",
  • "knowledge_base": true,
  • "drive": true
}

Change the workspace's security policy

Admins. The policy applies to the workspace's vaults at once: a refused request gets 403 with a policy_... error code (policy_two_factor_required, policy_session_expired, policy_client_not_allowed, policy_mcp_off, policy_mcp_read_only, policy_knowledge_base_off). Requiring two-step verification needs it on for the admin first.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
Request Body schema: application/json
required
require_two_factor
required
boolean

Members need two-step verification to use the workspace's vaults.

session_hours
required
integer [ 0 .. 8760 ]

How long after signing in a session may use the workspace's vaults (0 for no limit).

clients
required
Array of strings non-empty
Items Enum: "web" "desktop" "mobile" "api"

Apps allowed to use the workspace's vaults (api covers API tokens, OAuth apps, and the command line).

mcp
required
string
Enum: "on" "read_only" "off"

What AI tools connected through MCP may do.

knowledge_base
required
boolean
drive
required
boolean

Google Drive references (when available).

Responses

Request samples

Content type
application/json
{
  • "require_two_factor": true,
  • "session_hours": 0,
  • "clients": [
    ],
  • "mcp": "on",
  • "knowledge_base": true,
  • "drive": true
}

Response samples

Content type
application/json
{
  • "require_two_factor": true,
  • "session_hours": 0,
  • "clients": [
    ],
  • "mcp": "on",
  • "knowledge_base": true,
  • "drive": true
}

Where audit events are streamed

Admins. 404 when there is no stream.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "url": "string",
  • "secret": "string",
  • "last_error": "string"
}

Stream audit events to an endpoint

Admins. New events are POSTed in batches ({"workspace_id", "events": [...]}) within a minute, signed like webhooks (Granite-Signature). The secret is returned only here.

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>
Request Body schema: application/json
required
url
required
string <= 2000 characters

Responses

Request samples

Content type
application/json
{
  • "url": "string"
}

Response samples

Content type
application/json
{
  • "url": "string",
  • "secret": "string",
  • "last_error": "string"
}

Stop streaming audit events

Authorizations:
bearerAuth
path Parameters
workspace_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

comments

Comments on notes, kept outside the Markdown file. A thread's first comment is anchored to a quote of the text with a little context before and after; clients find the quote again after edits. Mentioned people who can read the note are notified.

A note's comments

Oldest first; replies have parent_id.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Comment on a note

Needs comment access to the note. mentions lists people to notify (those who cannot read the note are skipped).

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>
Request Body schema: application/json
required
body
required
string [ 1 .. 10000 ] characters
object (CommentAnchor)
parent_id
string <uuid>
mentions
Array of strings <uuid> <= 50 items [ items <uuid > ]

Responses

Request samples

Content type
application/json
{
  • "body": "string",
  • "anchor": {
    },
  • "parent_id": "1c6ca187-e61f-4301-8dcb-0e9749e89eef",
  • "mentions": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "parent_id": "1c6ca187-e61f-4301-8dcb-0e9749e89eef",
  • "author_id": "78424c75-5c41-4b25-9735-3c9f7d05c59e",
  • "author_email": "string",
  • "author_name": "string",
  • "body": "string",
  • "anchor": {
    },
  • "resolved_at": "2019-08-24T14:15:22Z",
  • "edited_at": "2019-08-24T14:15:22Z",
  • "deleted": true,
  • "created_at": "2019-08-24T14:15:22Z"
}

Edit or resolve a comment

Only the author edits body; anyone who may comment resolves a thread.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
comment_id
required
string <uuid>
Request Body schema: application/json
required
body
string [ 1 .. 10000 ] characters
resolved
boolean

Responses

Request samples

Content type
application/json
{
  • "body": "string",
  • "resolved": true
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Delete a comment

The author or a vault admin. Replies stay.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
comment_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Your notifications

Authorizations:
bearerAuth
query Parameters
cursor
string <= 512 characters

Opaque cursor from a previous page's next_cursor.

limit
integer <int32> [ 1 .. 200 ]
Default: 50

Maximum items to return.

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "unread": 0,
  • "next_cursor": "string"
}

Mark notifications read

The listed ones, or all when ids is empty.

Authorizations:
bearerAuth
Request Body schema: application/json
required
ids
Array of strings <uuid> <= 200 items [ items <uuid > ]

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ]
}

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

search

Full-text search over notes.

Full-text search in a vault

Searches the notes of one vault the caller may read. Query syntax: words match by prefix and all must match; "a phrase"; -word excludes; tag:x (also finds #x/y), path:x, and file:x filter. Matching ignores case and accents. Notes are indexed a few seconds after each commit; pending counts notes not indexed yet.

Authorizations:
bearerAuth
query Parameters
vault_id
required
string <uuid>
q
required
string [ 1 .. 500 ] characters
limit
integer [ 1 .. 50 ]
Default: 20

Responses

Response samples

Content type
application/json
{
  • "results": [
    ],
  • "pending": 0
}

knowledge base

Retrieval for AI tools (Pro and Business). Granite has no built-in assistant: tools search the knowledge base and get ranked passages with citations, then answer with their own model. Only vaults the caller may read are searched.

Whether the vault and which folders are in the knowledge base

Vault admins.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "included": true,
  • "excluded_folders": [
    ],
  • "exclude_drive": true
}

Choose whether the vault and which folders are in the knowledge base

Vault admins. Applies at once: an excluded folder's passages are removed from the index in this request, and files that come back in are queued for indexing.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
Request Body schema: application/json
required
included
required
boolean

The vault is in the knowledge base (when the plan includes it).

excluded_folders
required
Array of strings <= 100 items

Folders (with everything inside) left out of the knowledge base.

exclude_drive
required
boolean

Leave Google Drive references out.

Responses

Request samples

Content type
application/json
{
  • "included": true,
  • "excluded_folders": [
    ],
  • "exclude_drive": true
}

Response samples

Content type
application/json
{
  • "included": true,
  • "excluded_folders": [
    ],
  • "exclude_drive": true
}

How far the vault's indexing has got

Vault admins.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "available": true,
  • "indexed": true,
  • "documents": 0,
  • "chunks": 0,
  • "pending": 0,
  • "last_indexed_at": "2019-08-24T14:15:22Z",
  • "failures": [
    ]
}

Index the vault again

Vault admins. Queues every file; unchanged files are skipped quickly, failed ones are tried again.

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "queued": 0
}

Search the knowledge base

Ranks note and document passages (chunks split by headings, pages of PDFs) by full-text relevance and, when semantic search is enabled, meaning, fused by reciprocal rank. Searches every vault the caller may read, or only vault_ids. Needs a plan with the knowledge base (403 plan_limit_knowledge_base); tokens need the kb:query scope. 60 searches a minute per caller (429 kb_rate_limited). Files are indexed within a minute of a change; pending counts files waiting.

Authorizations:
bearerAuth
Request Body schema: application/json
required
query
required
string [ 1 .. 2000 ] characters
vault_ids
Array of strings <uuid> <= 100 items [ items <uuid > ]
limit
integer [ 1 .. 50 ]
Default: 10

Responses

Request samples

Content type
application/json
{
  • "query": "string",
  • "vault_ids": [
    ],
  • "limit": 10
}

Response samples

Content type
application/json
{
  • "passages": [
    ],
  • "pending": 0
}

publish

Public pages of published notes.

Publish a note as a public page

Makes the note readable by anyone with the link at /p/{slug} (rendered on the server from the note's current version and sanitized). Calling it again changes the address or password. Needs vault admin access; the plan limits how many notes are published (plan_limit_published_notes).

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>
Request Body schema: application/json
required
slug
string^[a-z0-9][a-z0-9-]{2,63}$

The page address; omitted keeps the current one or picks a random one.

password
string <= 256 characters

Omitted keeps the current password; "" removes it.

Responses

Request samples

Content type
application/json
{
  • "slug": "string",
  • "password": "string"
}

Response samples

Content type
application/json
{
  • "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
  • "path": "string",
  • "slug": "string",
  • "protected": true
}

Take a note's page down

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>
file_id
required
string <uuid>

Responses

Response samples

Content type
application/problem+json
{
  • "title": "string",
  • "status": 0,
  • "detail": "string",
  • "instance": "string",
  • "code": "string",
  • "request_id": "string",
  • "errors": [
    ]
}

Published notes in a vault

Authorizations:
bearerAuth
path Parameters
vault_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}