Granite Notes API

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.

GitHub OpenAPI

Operations 8

GET /v1/vaults/{vault_id}/notes List notes #
POST /v1/vaults/{vault_id}/notes Create a note #
GET /v1/vaults/{vault_id}/notes/by-path Read a note by its path #
GET /v1/vaults/{vault_id}/notes/{file_id} Read a note #
PUT /v1/vaults/{vault_id}/notes/{file_id} Replace a note's text #
PATCH /v1/vaults/{vault_id}/notes/{file_id} Edit part of a note #
DELETE /v1/vaults/{vault_id}/notes/{file_id} Delete a note #
POST /v1/vaults/{vault_id}/notes/{file_id}/move Rename or move a note #

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.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/granite-md:granite-md-notes-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

granite-md-notes-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Granite Notes 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: Notes
  description: '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.'
paths:
  /v1/vaults/{vault_id}/notes:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    get:
      operationId: listNotes
      tags:
      - Notes
      summary: List notes
      description: '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`.'
      parameters:
      - name: folder
        in: query
        required: false
        schema:
          type: string
          maxLength: 1024
      - $ref: '#/components/parameters/Cursor'
      - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Notes, without their text.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NoteList'
        default:
          $ref: '#/components/responses/Error'
    post:
      operationId: createNote
      tags:
      - Notes
      summary: Create a note
      description: '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`, `sync`, or

        `notes:create` (with `folders:create` too when the note''s folder does not exist yet).'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NoteCreate'
      responses:
        '201':
          description: The note.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        default:
          $ref: '#/components/responses/Error'
  /v1/vaults/{vault_id}/notes/by-path:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    get:
      operationId: getNoteByPath
      tags:
      - Notes
      summary: Read a note by its path
      parameters:
      - name: path
        in: query
        required: true
        schema:
          type: string
          minLength: 1
          maxLength: 1024
      responses:
        '200':
          description: The note with its text.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        default:
          $ref: '#/components/responses/Error'
  /v1/vaults/{vault_id}/notes/{file_id}:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    - $ref: '#/components/parameters/FileID'
    get:
      operationId: getNote
      tags:
      - Notes
      summary: Read a note
      responses:
        '200':
          description: The note with its text.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        default:
          $ref: '#/components/responses/Error'
    put:
      operationId: updateNote
      tags:
      - Notes
      summary: Replace a note's text
      description: 'With `base_version_id`, answers 409 `version_mismatch` if the note changed since that

        version; without it, the last write wins. Needs `vaults:write`, `sync`, or `notes:edit`.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NoteUpdate'
      responses:
        '200':
          description: The note.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        default:
          $ref: '#/components/responses/Error'
    patch:
      operationId: patchNote
      tags:
      - Notes
      summary: Edit part of a note
      description: '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`. Needs `vaults:write`, `sync`, or `notes:edit`.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NotePatch'
      responses:
        '200':
          description: The note.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        default:
          $ref: '#/components/responses/Error'
    delete:
      operationId: deleteNote
      tags:
      - Notes
      summary: Delete a note
      description: 'The note stays in version history and can be restored from there. Needs

        `vaults:write`, `sync`, or `notes:edit`.'
      parameters:
      - name: base_version_id
        in: query
        required: false
        schema:
          type: string
          format: uuid
      responses:
        '204':
          description: Deleted.
        default:
          $ref: '#/components/responses/Error'
  /v1/vaults/{vault_id}/notes/{file_id}/move:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    - $ref: '#/components/parameters/FileID'
    post:
      operationId: moveNote
      tags:
      - Notes
      summary: Rename or move a note
      description: 'Links to the note are not rewritten. A taken path answers 409 `path_taken`. Needs

        `vaults:write`, `sync`, or `notes:edit` (with `folders:create` too when the folder it

        moves into does not exist yet).'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NoteMove'
      responses:
        '200':
          description: The note, without its text.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        default:
          $ref: '#/components/responses/Error'
components:
  schemas:
    NoteList:
      type: object
      required:
      - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Note'
        next_cursor:
          type: string
    NoteCreate:
      type: object
      required:
      - path
      - content
      properties:
        path:
          type: string
          minLength: 1
          maxLength: 1024
        content:
          type: string
    Note:
      type: object
      required:
      - id
      - path
      - version_id
      - size
      - updated_at
      properties:
        id:
          type: string
          format: uuid
        path:
          type: string
        version_id:
          type: string
          format: uuid
        size:
          type: integer
          format: int64
        updated_at:
          type: string
          format: date-time
        content:
          type: string
          description: The note's text; omitted in lists and after a move.
    NoteUpdate:
      type: object
      required:
      - content
      properties:
        content:
          type: string
        base_version_id:
          type: string
          format: uuid
    NotePatch:
      type: object
      properties:
        find:
          type: string
          minLength: 1
        replace:
          type: string
        all:
          type: boolean
          default: false
        prepend:
          type: string
        append:
          type: string
        base_version_id:
          type: string
          format: uuid
    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'
    NoteMove:
      type: object
      required:
      - path
      properties:
        path:
          type: string
          minLength: 1
          maxLength: 1024
        base_version_id:
          type: string
          format: uuid
    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
  headers:
    RequestID:
      description: Identifier of this request, for support and log correlation.
      schema:
        type: string
  parameters:
    VaultID:
      name: vault_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    FileID:
      name: file_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
  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.