Hjarni Tags API

Tags that label notes across folders.

GitHub OpenAPI

Operations 8

GET /tags List tags #
POST /tags Create a tag #
GET /tags/{id} Get a tag #
PATCH /tags/{id} Rename a tag #
PUT /tags/{id} Rename a tag (PUT) #
DELETE /tags/{id} Delete a tag #
PATCH /tags/{id}/merge Merge a tag into another #
GET /tags/{id}/notes List a tag's notes #

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/hjarni-com:hjarni-com-tags-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

hjarni-com-tags-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Hjarni REST Tags API
  version: 1.0.0
  description: Hjarni is a Markdown knowledge base that AI assistants and people share; this API reads and writes the same notes, folders (called containers) and tags that its MCP server exposes. Requests go to https://hjarni.com/api/v1 with a bearer token in the Authorization header, send and receive JSON, and list endpoints page with `page` and `per_page` and report totals in the `X-Total-Count`, `X-Page` and `X-Per-Page` headers. Requests are limited to 100 a minute per account across all of its tokens, and going over answers 429 with a `Retry-After` header.
  termsOfService: https://hjarni.com/terms
  contact:
    name: Hjarni
    email: evert@hjarni.com
    url: https://hjarni.com/docs/api
servers:
- url: https://hjarni.com/api/v1
security:
- bearerAuth: []
tags:
- name: Tags
  description: Tags that label notes across folders.
paths:
  /tags:
    get:
      operationId: listTags
      summary: List tags
      description: Lists the person's tags by name, each with the number of non-trashed notes carrying it. An agent token sees only tags on notes it can read, counted over those notes. Paginated.
      tags:
      - Tags
      parameters:
      - $ref: '#/components/parameters/Page'
      - $ref: '#/components/parameters/PerPage'
      responses:
        '200':
          description: One page of tags.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
            X-Page:
              $ref: '#/components/headers/X-Page'
            X-Per-Page:
              $ref: '#/components/headers/X-Per-Page'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Tag'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    post:
      operationId: createTag
      summary: Create a tag
      description: Creates a tag and returns it with 201. Names are unique per person (422 for a duplicate). To tag a note, set the note's `tag_list` instead, which also creates missing tags. Agent tokens are refused with 403.
      tags:
      - Tags
      requestBody:
        $ref: '#/components/requestBodies/TagCreate'
      responses:
        '201':
          description: The created tag.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tag'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /tags/{id}:
    parameters:
    - $ref: '#/components/parameters/TagId'
    get:
      operationId: getTag
      summary: Get a tag
      description: Returns one tag with its note count. An agent token can read only tags on notes it can read; others answer 404.
      tags:
      - Tags
      responses:
        '200':
          description: The tag.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tag'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    patch:
      operationId: updateTag
      summary: Rename a tag
      description: Renames the tag everywhere it is used and returns it. Agent tokens are refused with 403.
      tags:
      - Tags
      requestBody:
        $ref: '#/components/requestBodies/TagUpdate'
      responses:
        '200':
          description: The renamed tag.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tag'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      operationId: updateTagWithPut
      summary: Rename a tag (PUT)
      description: The same action as PATCH /tags/{id}. Prefer PATCH.
      tags:
      - Tags
      requestBody:
        $ref: '#/components/requestBodies/TagUpdate'
      responses:
        '200':
          description: The renamed tag.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tag'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    delete:
      operationId: deleteTag
      summary: Delete a tag
      description: Deletes the tag and answers 204. Notes keep their content; only the tag is removed from them. This cannot be undone. Agent tokens are refused with 403.
      tags:
      - Tags
      responses:
        '204':
          description: The tag is deleted.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /tags/{id}/merge:
    parameters:
    - $ref: '#/components/parameters/TagId'
    patch:
      operationId: mergeTag
      summary: Merge a tag into another
      description: Moves every note carrying this tag onto `target_id`, deletes this tag, and returns the target tag. Merging a tag into itself is not handled and fails with a 500. Agent tokens are refused with 403.
      tags:
      - Tags
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - target_id
              properties:
                target_id:
                  type: integer
                  description: The tag that survives. Missing or unknown answers 404.
            example:
              target_id: 5
      responses:
        '200':
          description: The target tag, after the merge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tag'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /tags/{id}/notes:
    parameters:
    - $ref: '#/components/parameters/TagId'
    get:
      operationId: listTagNotes
      summary: List a tag's notes
      description: Lists the active notes carrying this tag, with bodies, in the same order as GET /notes. An agent token sees only the notes it can read. Paginated.
      tags:
      - Tags
      parameters:
      - $ref: '#/components/parameters/Page'
      - $ref: '#/components/parameters/PerPage'
      responses:
        '200':
          description: One page of notes with this tag.
          headers:
            X-Total-Count:
              $ref: '#/components/headers/X-Total-Count'
            X-Page:
              $ref: '#/components/headers/X-Page'
            X-Per-Page:
              $ref: '#/components/headers/X-Per-Page'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/NoteSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  responses:
    UnprocessableEntity:
      description: The request was understood but not applied. Model validations answer with an `errors` array of messages; parameter checks (an unknown note key, a surgical edit whose anchor does not match, a missing file `url`, `ordered_ids` that do not match the folders in scope, a URL that could not be fetched) answer with an `errors` array or a single `error` string, as noted on each endpoint.
      content:
        application/json:
          schema:
            anyOf:
            - $ref: '#/components/schemas/ValidationErrors'
            - $ref: '#/components/schemas/Error'
          examples:
            validation:
              summary: Model validation
              value:
                errors:
                - Title can't be blank
            parameterCheck:
              summary: Parameter check
              value:
                error: ordered_ids must contain exactly all containers in this scope
    NotFound:
      description: No record with that id that this token can reach (including notes and folders in the Trash, except where an endpoint says otherwise), or no endpoint for this method and path. A revision that does not exist answers with `error` only.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            status: 404
            error: Not Found
            code: not_found
            message: No endpoint matches this method and path, or the record is not one this token can reach. Every endpoint is listed in https://hjarni.com/openapi.json.
            docs_url: https://hjarni.com/docs/api#errors
    Forbidden:
      description: 'Refused, for one of three reasons. (1) A plan limit: the Free plan''s note limit on create or restore, the Free file allowance on an attachment, or an account that is read-only because it holds more notes than its plan carries. The body has `error` and an `upgrade_url`, and the personal note-limit refusal also a `team_url`. (2) An agent token outside its grants: reading or writing where it was not given access, writing where it only reads, or calling an endpoint agent tokens cannot use. The body has `error` "Forbidden: outside token scope" with `code` `out_of_scope`, `message` and `docs_url`, and no `upgrade_url`. (3) A team folder where the person''s role is below editor: `error` only, "You don''t have permission to do that (editor required)."'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            outOfScope:
              summary: Agent token outside its grants
              value:
                error: 'Forbidden: outside token scope'
                code: out_of_scope
                message: This token cannot do this. An agent token reaches only the folders it was given, at the level it was given, and never manages tags, personal instructions or folder order; those need a full-access token. A token's access is set in Settings > Connections.
                docs_url: https://hjarni.com/docs/agent-tokens
            noteLimit:
              summary: Free plan note limit reached
              value:
                error: You've reached the 25 personal note limit on the Free plan. Upgrade to Pro for unlimited notes, or start a shared team knowledge base if this should live with your teammates.
                upgrade_url: /checkout/new?reason=note_limit&ref=paywall_note_limit&t=CHECKOUT_TOKEN
                team_url: /teams/new?ref=paywall_note_limit
            editorRequired:
              summary: Team role below editor
              value:
                error: You don't have permission to do that (editor required).
    TooManyRequests:
      description: Over the rate limit of 100 requests a minute per account, counted across all of its tokens (counters are kept per server process, so the ceiling actually enforced can be somewhat higher). Wait the number of seconds in `Retry-After`. The body has `error` only.
      headers:
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Rate limit exceeded. Retry after 12 seconds.
    BadRequest:
      description: The body could not be read (malformed JSON), or a required wrapper or parameter such as `note`, `container`, `tag` or `ordered_ids` is missing or empty.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            status: 400
            error: Bad Request
            code: bad_request
            message: The ordered_ids parameter is missing or empty. Each endpoint's request body is in https://hjarni.com/openapi.json.
            docs_url: https://hjarni.com/docs/api#errors
    Unauthorized:
      description: The bearer token is missing, unknown or expired.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Unauthorized
            code: unauthorized
            message: 'Missing or invalid token. Send it as Authorization: Bearer <token>; tokens are created in Settings > Connections.'
            docs_url: https://hjarni.com/docs/api#auth
  parameters:
    Page:
      name: page
      in: query
      description: Page number, starting at 1. Values below 1 are treated as 1.
      schema:
        type: integer
        minimum: 1
        default: 1
    TagId:
      name: id
      in: path
      required: true
      description: The tag's id.
      schema:
        type: integer
    PerPage:
      name: per_page
      in: query
      description: Items per page. Values outside 1-100 are clamped.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
  schemas:
    TeamRef:
      type: object
      required:
      - id
      - name
      properties:
        id:
          type: integer
        name:
          type:
          - string
          - 'null'
    NoteSummary:
      type: object
      description: A note as list endpoints return it. `body` is left out when the request sets `exclude_body=true`; `last_edited_by` and `last_edited_by_agent` appear only once someone has edited the note; `team` appears only on team notes.
      required:
      - id
      - title
      - summary
      - summary_stale
      - source_url
      - archived
      - favorited
      - position
      - lock_version
      - created_at
      - updated_at
      - tag_list
      - tags
      - container
      - files
      properties:
        id:
          type: integer
        title:
          type: string
        body:
          type:
          - string
          - 'null'
          description: Markdown. `[[id:Title]]` is a wiki-link to another note.
        summary:
          type:
          - string
          - 'null'
          description: A short summary for scanning.
        summary_stale:
          type: boolean
          description: True when the body changed after the summary was written.
        source_url:
          type:
          - string
          - 'null'
        archived:
          type: boolean
        favorited:
          type: boolean
        position:
          type:
          - integer
          - 'null'
          description: Sort position among its siblings; null when unpositioned.
        lock_version:
          type: integer
          description: Increases on every save. Send it back as expected_lock_version to guard a write.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        last_edited_by:
          type:
          - string
          - 'null'
          description: Email address of whoever saved the note last.
        last_edited_by_agent:
          type:
          - string
          - 'null'
          description: Name of the agent or token that saved last, if one did.
        tag_list:
          type: string
          description: Tag names joined by a comma and a space, e.g. "planning, review".
        tags:
          type: array
          items:
            $ref: '#/components/schemas/TagRef'
        container:
          description: The folder the note is filed in; null for the inbox.
          oneOf:
          - $ref: '#/components/schemas/ContainerRef'
          - type: 'null'
        team:
          $ref: '#/components/schemas/TeamRef'
        files:
          type: array
          items:
            $ref: '#/components/schemas/NoteAttachment'
    Tag:
      type: object
      required:
      - id
      - name
      - created_at
      - updated_at
      - notes_count
      properties:
        id:
          type: integer
        name:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        notes_count:
          type: integer
          description: Notes carrying the tag that are not in the Trash, archived included.
    NoteAttachment:
      type: object
      description: A file attached to a note.
      required:
      - id
      - description
      - filename
      - content_type
      - byte_size
      - url
      - url_expires_at
      properties:
        id:
          type: integer
        description:
          type:
          - string
          - 'null'
        filename:
          type: string
        content_type:
          type:
          - string
          - 'null'
        byte_size:
          type: integer
        url:
          type: string
          format: uri
          description: Signed download link that works without a token until `url_expires_at` (15 minutes). Treat it as a credential and do not store it; fetch the note again for a fresh one.
        url_expires_at:
          type: string
          format: date-time
    ContainerRef:
      type: object
      required:
      - id
      - name
      properties:
        id:
          type: integer
        name:
          type: string
    Error:
      type: object
      description: 'Every error response carries `error`. The generic statuses also carry `code`, `message` and `docs_url`: 401, a 403 for an agent token outside its grants, and any status answered for a raised error or an unknown endpoint (400, 404, 5xx). Plan-limit 403s carry `upgrade_url` instead. Some endpoint-specific errors carry `error` alone.'
      required:
      - error
      properties:
        status:
          type: integer
          description: The HTTP status again. Present on errors raised inside the app and on unknown endpoints (400, 404, 5xx), not on 401 or the 403s.
        error:
          type: string
          description: Human-readable message.
        errors:
          type: array
          items:
            type: string
          description: 'Only on a validation failure raised inside the app (422): one message per problem.'
        code:
          type: string
          description: 'Machine-readable code: `unauthorized` (401), `out_of_scope` (403, agent token), or for a raised error or unknown endpoint the snake_case name of the HTTP status, such as `bad_request`, `not_found`, `unprocessable_content` or `internal_server_error`.'
          examples:
          - unauthorized
          - out_of_scope
          - not_found
          - internal_server_error
        message:
          type: string
          description: What to do about it.
        docs_url:
          type: string
          format: uri
          description: The documentation page that explains this error.
        upgrade_url:
          type: string
          format: uri-reference
          description: Only on plan-limit 403s. A path on hjarni.com (relative, so prefix https://hjarni.com) to the checkout that lifts the limit.
        team_url:
          type: string
          format: uri-reference
          description: Only on the personal note-limit 403 from POST /notes. A relative path to creating a team, the other way past the personal limit.
    TagRef:
      type: object
      required:
      - id
      - name
      properties:
        id:
          type: integer
        name:
          type: string
    ValidationErrors:
      type: object
      description: The 422 model-validation shape, also used by a few 409s.
      required:
      - errors
      properties:
        errors:
          type: array
          items:
            type: string
          description: One human-readable message per problem.
  requestBodies:
    TagUpdate:
      required: true
      description: Fields go in a `tag` object; a top-level `name` is wrapped into it for you.
      content:
        application/json:
          schema:
            type: object
            required:
            - tag
            properties:
              tag:
                type: object
                properties:
                  name:
                    type: string
                    description: The new name, unique per person.
          example:
            tag:
              name: new-name
    TagCreate:
      required: true
      description: Fields go in a `tag` object; a top-level `name` is wrapped into it for you.
      content:
        application/json:
          schema:
            type: object
            required:
            - tag
            properties:
              tag:
                type: object
                required:
                - name
                properties:
                  name:
                    type: string
                    description: The tag name, unique per person. Stored as sent (not lowercased).
          example:
            tag:
              name: research
  headers:
    Retry-After:
      description: Seconds until the current rate-limit window ends.
      schema:
        type: integer
    X-Per-Page:
      description: The page size used, after clamping.
      schema:
        type: integer
    X-Total-Count:
      description: Total number of records matching the query, across all pages.
      schema:
        type: integer
    X-Page:
      description: The page returned.
      schema:
        type: integer
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'An API token in `Authorization: Bearer <token>`. Tokens are created in Settings > Connections, or issued to an assistant through the OAuth flow. A new token made in Settings reaches only the folders it is given, at Read or Write per folder, unless it is made with Everything I can reach (see https://hjarni.com/docs/agent-tokens); a token issued through OAuth acts with the person''s full access. A missing, unknown or expired token answers 401.'
externalDocs:
  url: https://hjarni.com/docs/api