Work with this as data
Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/granite-md:granite-md-sync-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
OpenAPI Specification
openapi: 3.2.0
info:
title: Granite Sync API
version: 1.0.0
summary: REST API for Granite clients, bots, and integrations.
description: '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/`; `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 ` 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).'
license:
name: Proprietary
identifier: LicenseRef-Proprietary
servers:
- url: https://api.granite.md
description: Production
- url: http://localhost:8080
description: Local development
security:
- bearerAuth: []
tags:
- name: Sync
description: The sync protocol (PLAN.md Appendix B).
paths:
/v1/vaults/{vault_id}/tree:
parameters:
- $ref: '#/components/parameters/VaultID'
get:
operationId: getTree
tags:
- Sync
summary: Snapshot of the vault's files
description: '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).'
parameters:
- name: at
in: query
required: false
schema:
type: integer
format: int64
minimum: 0
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: A page of the snapshot.
content:
application/json:
schema:
$ref: '#/components/schemas/TreePage'
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/changes:
parameters:
- $ref: '#/components/parameters/VaultID'
get:
operationId: getChanges
tags:
- Sync
summary: Changes after a sequence number
description: Answers 410 with code `cursor_expired` when `since` is older than the kept log.
parameters:
- name: since
in: query
required: true
schema:
type: integer
format: int64
minimum: 0
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 1000
default: 500
responses:
'200':
description: Changes in order.
content:
application/json:
schema:
$ref: '#/components/schemas/ChangesPage'
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/files/{file_id}/info:
parameters:
- $ref: '#/components/parameters/VaultID'
- $ref: '#/components/parameters/FileID'
get:
operationId: getNoteInfo
tags:
- Sync
summary: Who created and last changed a note, and who can open it
description: Anyone who can read the note. `created_by` is absent once history no longer reaches the first version.
responses:
'200':
description: OK.
content:
application/json:
schema:
$ref: '#/components/schemas/NoteInfo'
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/commit:
parameters:
- $ref: '#/components/parameters/VaultID'
post:
operationId: commitChanges
tags:
- Sync
summary: Apply a batch of changes atomically
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CommitRequest'
responses:
'200':
description: Every op applied.
content:
application/json:
schema:
$ref: '#/components/schemas/CommitResult'
'409':
description: Nothing applied; every conflicting op is listed (code `commit_conflict`).
content:
application/problem+json:
schema:
$ref: '#/components/schemas/CommitConflictProblem'
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/blobs/check:
parameters:
- $ref: '#/components/parameters/VaultID'
post:
operationId: checkBlobs
tags:
- Sync
summary: Which blobs still need uploading
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BlobCheckRequest'
responses:
'200':
description: Missing hashes.
content:
application/json:
schema:
$ref: '#/components/schemas/BlobCheckResult'
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/blobs/{hash}:
parameters:
- $ref: '#/components/parameters/VaultID'
- $ref: '#/components/parameters/BlobHash'
put:
operationId: uploadBlob
tags:
- Sync
summary: Upload a blob (up to 8 MiB)
description: The server checks the bytes against the hash and the plan's limits. Larger blobs use upload sessions.
requestBody:
required: true
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
'204':
description: Stored (or already present).
default:
$ref: '#/components/responses/Error'
get:
operationId: downloadBlob
tags:
- Sync
summary: Download a blob
parameters:
- name: Range
in: header
required: false
description: One range, `bytes=start-end` or `bytes=start-`.
schema:
type: string
maxLength: 100
responses:
'200':
description: The whole blob.
content:
application/octet-stream:
schema:
type: string
format: binary
'206':
description: The requested range.
headers:
Content-Range:
schema:
type: string
content:
application/octet-stream:
schema:
type: string
format: binary
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/uploads:
parameters:
- $ref: '#/components/parameters/VaultID'
post:
operationId: createUpload
tags:
- Sync
summary: Start a resumable upload
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UploadCreate'
responses:
'201':
description: Session created.
content:
application/json:
schema:
$ref: '#/components/schemas/UploadStatus'
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/uploads/{upload_id}:
parameters:
- $ref: '#/components/parameters/VaultID'
- $ref: '#/components/parameters/UploadID'
get:
operationId: getUpload
tags:
- Sync
summary: How much of an upload arrived
responses:
'200':
description: Status.
content:
application/json:
schema:
$ref: '#/components/schemas/UploadStatus'
default:
$ref: '#/components/responses/Error'
patch:
operationId: appendUpload
tags:
- Sync
summary: Send the next part (up to 16 MiB)
description: '`Upload-Offset` must equal the bytes received so far (code `upload_offset_mismatch` otherwise).'
parameters:
- name: Upload-Offset
in: header
required: true
schema:
type: integer
format: int64
minimum: 0
requestBody:
required: true
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
'200':
description: Status after this part.
content:
application/json:
schema:
$ref: '#/components/schemas/UploadStatus'
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/uploads/{upload_id}/finish:
parameters:
- $ref: '#/components/parameters/VaultID'
- $ref: '#/components/parameters/UploadID'
post:
operationId: finishUpload
tags:
- Sync
summary: Finish an upload
description: Checks the bytes against the hash and stores the blob.
responses:
'204':
description: Stored.
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/files/{file_id}/versions:
parameters:
- $ref: '#/components/parameters/VaultID'
- $ref: '#/components/parameters/FileID'
get:
operationId: listVersions
tags:
- Sync
summary: A file's version history, newest first
description: Superseded versions are kept for the plan's history period (PLAN.md 1.2).
parameters:
- name: before
in: query
required: false
description: Return versions with a lower seq (from `next_before` of the previous page).
schema:
type: integer
format: int64
minimum: 1
- $ref: '#/components/parameters/Limit'
responses:
'200':
description: A page of versions.
content:
application/json:
schema:
$ref: '#/components/schemas/VersionList'
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/files/{file_id}/versions/{version_id}:
parameters:
- $ref: '#/components/parameters/VaultID'
- $ref: '#/components/parameters/FileID'
- $ref: '#/components/parameters/VersionID'
get:
operationId: getVersion
tags:
- Sync
summary: One version
description: Download its content with `GET /v1/vaults/{vault_id}/blobs/{blob_hash}`.
responses:
'200':
description: The version.
content:
application/json:
schema:
$ref: '#/components/schemas/Version'
default:
$ref: '#/components/responses/Error'
/v1/vaults/{vault_id}/files/{file_id}/versions/{version_id}/restore:
parameters:
- $ref: '#/components/parameters/VaultID'
- $ref: '#/components/parameters/FileID'
- $ref: '#/components/parameters/VersionID'
post:
operationId: restoreVersion
tags:
- Sync
summary: Make an old version current again
description: Commits a new version with the old content at the file's current path (a deleted file comes back).
responses:
'200':
description: Restored.
content:
application/json:
schema:
$ref: '#/components/schemas/CommitResult'
'409':
description: The file changed meanwhile.
content:
application/problem+json:
schema:
$ref: '#/components/schemas/CommitConflictProblem'
default:
$ref: '#/components/responses/Error'
components:
schemas:
CommitConflict:
type: object
required:
- op_index
- code
properties:
op_index:
type: integer
code:
type: string
enum:
- version_mismatch
- path_taken
- file_not_found
current:
$ref: '#/components/schemas/FileState'
ChangesPage:
type: object
required:
- changes
- head_seq
- has_more
properties:
changes:
type: array
items:
$ref: '#/components/schemas/Change'
head_seq:
type: integer
format: int64
has_more:
type: boolean
UploadStatus:
type: object
required:
- upload_id
- hash
- size
- received
properties:
upload_id:
type: string
hash:
type: string
size:
type: integer
format: int64
received:
type: integer
format: int64
FieldError:
type: object
required:
- field
- detail
properties:
field:
type: string
description: JSON path of the invalid field, for example `ops[2].path`.
detail:
type: string
Actor:
type: object
required:
- id
- email
- name
properties:
id:
type: string
format: uuid
email:
type: string
name:
type: string
CommitConflictProblem:
type: object
description: A problem document (type conflict, code commit_conflict) with the conflicts.
required:
- type
- title
- status
- conflicts
properties:
type:
type: string
title:
type: string
status:
type: integer
detail:
type: string
code:
type: string
request_id:
type: string
conflicts:
type: array
items:
$ref: '#/components/schemas/CommitConflict'
TreePage:
type: object
required:
- head_seq
- files
properties:
head_seq:
type: integer
format: int64
description: The seq this snapshot reflects; pass it as `at` for later pages.
files:
type: array
items:
$ref: '#/components/schemas/TreeFile'
next_cursor:
type: string
Restriction:
type: object
required:
- id
- folder
- private
- created_at
properties:
id:
type: string
format: uuid
folder:
type: string
description: The restricted folder or note.
private:
type: boolean
private_to:
type: string
format: uuid
description: The member a private folder belongs to.
created_at:
type: string
format: date-time
Problem:
type: object
description: RFC 9457 problem details.
required:
- type
- title
- status
properties:
type:
type: string
format: uri
examples:
- https://granite.md/problems/conflict
title:
type: string
status:
type: integer
format: int32
detail:
type: string
instance:
type: string
code:
type: string
description: Machine-readable refinement of `type`, for example `version_conflict`.
request_id:
type: string
errors:
type: array
description: Per-field validation errors.
items:
$ref: '#/components/schemas/FieldError'
CommitOpResult:
type: object
required:
- op_index
- file_id
- version_id
- seq
properties:
op_index:
type: integer
file_id:
type: string
format: uuid
version_id:
type: string
format: uuid
seq:
type: integer
format: int64
BlobCheckResult:
type: object
required:
- missing
properties:
missing:
type: array
items:
type: string
CommitResult:
type: object
required:
- results
- head_seq
properties:
head_seq:
type: integer
format: int64
results:
type: array
items:
$ref: '#/components/schemas/CommitOpResult'
Change:
type: object
required:
- seq
- op
- file_id
- version_id
- path
- size
- content_type
- kind
- deleted
- author_id
- committed_at
properties:
seq:
type: integer
format: int64
op:
type: string
enum:
- put
- rename
- delete
file_id:
type: string
format: uuid
version_id:
type: string
format: uuid
path:
type: string
old_path:
type: string
blob_hash:
type: string
description: Absent for deletes.
size:
type: integer
format: int64
content_type:
type: string
kind:
type: string
enum:
- markdown
- text
- binary
deleted:
type: boolean
author_id:
type: string
format: uuid
device_id:
type: string
format: uuid
committed_at:
type: string
format: date-time
client_mtime:
type: string
format: date-time
FileState:
type: object
required:
- file_id
- version_id
- path
- deleted
properties:
file_id:
type: string
format: uuid
version_id:
type: string
format: uuid
path:
type: string
deleted:
type: boolean
TreeFile:
type: object
required:
- file_id
- version_id
- path
- blob_hash
- size
- content_type
- kind
properties:
file_id:
type: string
format: uuid
version_id:
type: string
format: uuid
path:
type: string
blob_hash:
type: string
size:
type: integer
format: int64
content_type:
type: string
kind:
type: string
enum:
- markdown
- text
- binary
client_mtime:
type: string
format: date-time
PathAccess:
type: object
required:
- path
- private
- restricted
- restricted_at
- can_manage
- can_make_private
- openers
properties:
path:
type: string
private:
type: boolean
description: Inside a private folder (only its member can open it).
restricted:
type: boolean
description: Inside a folder limited to its own grants.
restricted_at:
type: string
description: The innermost restricted or private folder covering the path ("" when none).
restriction:
$ref: '#/components/schemas/Restriction'
can_manage:
type: boolean
description: The caller may restrict the path and choose who opens it (workspace owners and admins).
can_make_private:
type: boolean
description: The caller may make it private, or lift its privacy.
openers:
type: array
items:
$ref: '#/components/schemas/Opener'
UploadCreate:
type: object
required:
- hash
- size
properties:
hash:
type: string
pattern: ^[0-9a-f]{64}$
size:
type: integer
format: int64
minimum: 0
CommitRequest:
type: object
required:
- ops
properties:
device_id:
type: string
format: uuid
ops:
type: array
maxItems: 1000
items:
$ref: '#/components/schemas/CommitOp'
NoteInfo:
type: object
required:
- file_id
- path
- created_at
- modified_at
- size
- versions
- access
- shared_with
properties:
file_id:
type: string
format: uuid
path:
type: string
created_at:
type: string
format: date-time
created_by:
$ref: '#/components/schemas/Actor'
modified_at:
type: string
format: date-time
modified_by:
$ref: '#/components/schemas/Actor'
size:
type: integer
format: int64
versions:
type: integer
format: int64
access:
$ref: '#/components/schemas/PathAccess'
shared_with:
type: array
description: Emails a personal note was shared with (the vault's owner sees them).
items:
type: string
BlobCheckRequest:
type: object
required:
- hashes
properties:
hashes:
type: array
maxItems: 1000
items:
type: string
pattern: ^[0-9a-f]{64}$
Opener:
type: object
required:
- kind
- access
- folder
properties:
kind:
type: string
enum:
- owner
- user
- group
- admin
description: The personal vault's owner, a member, a team (group), or a workspace owner or admin.
id:
type: string
format: uuid
name:
type: string
email:
type: string
access:
type: string
enum:
- read
- comment
- write
- admin
folder:
type: string
description: Where the grant was made; "" for the whole vault.
members:
type: integer
format: int64
description: For teams.
grant_id:
type: string
format: uuid
CommitOp:
type: object
required:
- type
description: See Appendix B.3 for which fields each type needs.
properties:
type:
type: string
enum:
- put
- rename
- delete
file_id:
type: string
format: uuid
base_version_id:
type: string
format: uuid
path:
type: string
maxLength: 4096
new_path:
type: string
maxLength: 4096
blob_hash:
type: string
maxLength: 64
size:
type: integer
format: int64
minimum: 0
content_type:
type: string
maxLength: 200
client_mtime:
type: string
format: date-time
VersionList:
type: object
required:
- versions
properties:
versions:
type: array
items:
$ref: '#/components/schemas/Version'
next_before:
type: integer
format: int64
Version:
type: object
required:
- id
- file_id
- seq
- op
- path
- size
- content_type
- deleted
- author_id
- created_at
properties:
id:
type: string
format: uuid
file_id:
type: string
format: uuid
seq:
type: integer
format: int64
op:
type: string
enum:
- put
- rename
- delete
path:
type: string
blob_hash:
type: string
description: Absent for deletions.
size:
type: integer
format: int64
content_type:
type: string
deleted:
type: boolean
author_id:
type: string
format: uuid
author_name:
type: string
description: The author's name ("" for bots and unknown authors).
author_email:
type: string
device_id:
type: string
format: uuid
created_at:
type: string
format: date-time
headers:
RequestID:
description: Identifier of this request, for support and log correlation.
schema:
type: string
parameters:
BlobHash:
name: hash
in: path
required: true
schema:
type: string
pattern: ^[0-9a-f]{64}$
UploadID:
name: upload_id
in: path
required: true
schema:
type: string
pattern: ^[0-9a-f]{32}$
IdempotencyKey:
name: Idempotency-Key
in: header
required: false
description: Makes a non-idempotent POST safe to retry. See the conventions above.
schema:
type: string
minLength: 1
maxLength: 255
pattern: ^[\x21-\x7E]+$
VaultID:
name: vault_id
in: path
required: true
schema:
type: string
format: uuid
Limit:
name: limit
in: query
required: false
description: Maximum items to return.
schema:
type: integer
format: int32
minimum: 1
maximum: 200
default: 50
Cursor:
name: cursor
in: query
required: false
description: Opaque cursor from a previous page's `next_cursor`.
schema:
type: string
maxLength: 512
VersionID:
name: version_id
in: path
required: true
schema:
type: string
format: uuid
FileID:
name: file_id
in: path
required: true
schema:
type: string
format: uuid
responses:
Error:
description: Error response.
headers:
X-Request-ID:
$ref: '#/components/headers/RequestID'
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: Session access token, personal API token, or OAuth access token.