Hubs

Content hub / category — hubs group posts into top-level sections with branding and per-language translations.

Service: blog.hubs

Content hub / category. Hubs group posts into top-level sections (e.g. “Security”, “Privacy”) and carry their own branding (icon, color) plus per-language translations.

Fields

Field Type Required Description
id string Primary key. Read-only, auto-generated
slug string URL-safe identifier, unique (format: slug)
title string Display title
tagline string? Short one-liner shown on the hub page header. null if unset
icon string Bootstrap Icons class for the hub badge / hero. Default "bi-grid"
color string? Brand color (hex); gradient end derived in CSS. null if unset
hub_translation HubTranslation[] Inline translations

HubTranslation

Per-language overlay for a hub. Translatable fields are nullable so a partial translation overlays onto the base hub without clobbering untranslated fields (null = inherit base).

Field Type Required Description
id string Primary key. Read-only. Include when updating existing rows
source_id string Hub being translated. Read-only, auto-set from parent
language_id string Language of this translation
title string? Translated hub title (null = inherit base)
tagline string? Translated hub tagline (null = inherit base)

Unique constraint: { source_id, language_id } — one translation per language per hub.

find

List hubs with filtering, sorting, and pagination.

HTTP

Request

GET /v1/blog/hubs?fields=id,slug,title,tagline,icon,color,hub_translation.id,hub_translation.language_id,hub_translation.title,hub_translation.tagline&limit=100
Authorization: Bearer <token>

Response

{
  "items": [
    {
      "id": "1920438291700",
      "slug": "security",
      "title": "Security",
      "tagline": "Encryption, privacy, and the threat model",
      "icon": "bi-shield-lock",
      "color": "#6f42c1",
      "hub_translation": [
        {
          "id": "1920438291705",
          "language_id": "2",
          "title": "Sécurité",
          "tagline": null
        }
      ]
    }
  ],
  "total": 1
}

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "blog.hubs.find",
  "params": {
    "fields": [
      "id", "slug", "title", "tagline", "icon", "color",
      "hub_translation.id", "hub_translation.language_id",
      "hub_translation.title", "hub_translation.tagline"
    ],
    "limit": 100
  },
  "id": 1
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "items": [
      {
        "id": "1920438291700",
        "slug": "security",
        "title": "Security",
        "tagline": "Encryption, privacy, and the threat model",
        "icon": "bi-shield-lock",
        "color": "#6f42c1",
        "hub_translation": [
          {
            "id": "1920438291705",
            "language_id": "2",
            "title": "Sécurité",
            "tagline": null
          }
        ]
      }
    ],
    "total": 1
  },
  "id": 1
}

findOneById

Fetch a single hub by id with full detail including translations.

HTTP

Request

GET /v1/blog/hubs/1920438291700?fields=id,slug,title,tagline,icon,color,hub_translation.id,hub_translation.language_id,hub_translation.title,hub_translation.tagline
Authorization: Bearer <token>

Response

{
  "id": "1920438291700",
  "slug": "security",
  "title": "Security",
  "tagline": "Encryption, privacy, and the threat model",
  "icon": "bi-shield-lock",
  "color": "#6f42c1",
  "hub_translation": [
    {
      "id": "1920438291705",
      "language_id": "2",
      "title": "Sécurité",
      "tagline": null
    }
  ]
}

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "blog.hubs.findOneById",
  "params": {
    "id": "1920438291700",
    "fields": [
      "id", "slug", "title", "tagline", "icon", "color",
      "hub_translation.id", "hub_translation.language_id",
      "hub_translation.title", "hub_translation.tagline"
    ]
  },
  "id": 2
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "id": "1920438291700",
    "slug": "security",
    "title": "Security",
    "tagline": "Encryption, privacy, and the threat model",
    "icon": "bi-shield-lock",
    "color": "#6f42c1",
    "hub_translation": [
      {
        "id": "1920438291705",
        "language_id": "2",
        "title": "Sécurité",
        "tagline": null
      }
    ]
  },
  "id": 2
}

create

Create a new hub. The server assigns id. Inline hub_translation entries without id are created alongside the hub.

HTTP

Request

POST /v1/blog/hubs
Authorization: Bearer <token>
Content-Type: application/json

{
  "slug": "security",
  "title": "Security",
  "tagline": "Encryption, privacy, and the threat model",
  "icon": "bi-shield-lock",
  "color": "#6f42c1",
  "hub_translation": [
    { "language_id": "2", "title": "Sécurité", "tagline": null }
  ]
}

Response

{
  "id": "1920438291700",
  "slug": "security",
  "title": "Security",
  "hub_translation": [
    { "id": "1920438291705", "language_id": "2", "title": "Sécurité" }
  ]
}

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "blog.hubs.create",
  "params": {
    "document": {
      "slug": "security",
      "title": "Security",
      "tagline": "Encryption, privacy, and the threat model",
      "icon": "bi-shield-lock",
      "color": "#6f42c1",
      "hub_translation": [
        { "language_id": "2", "title": "Sécurité", "tagline": null }
      ]
    }
  },
  "id": 3
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "id": "1920438291700",
    "slug": "security",
    "title": "Security",
    "hub_translation": [
      { "id": "1920438291705", "language_id": "2", "title": "Sécurité" }
    ]
  },
  "id": 3
}

updateOneById

Partial update — only specified fields change; omitted fields are untouched. Nested collections are updated automatically: items with id are updated, items without id are created, items missing from the array are deleted.

HTTP

Request

PATCH /v1/blog/hubs/1920438291700
Authorization: Bearer <token>
Content-Type: application/json

{
  "tagline": "Encryption, privacy, and our threat model",
  "color": "#6610f2"
}

Response

{
  "id": "1920438291700",
  "tagline": "Encryption, privacy, and our threat model",
  "color": "#6610f2"
}

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "blog.hubs.updateOneById",
  "params": {
    "id": "1920438291700",
    "document": {
      "tagline": "Encryption, privacy, and our threat model",
      "color": "#6610f2"
    }
  },
  "id": 4
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "id": "1920438291700",
    "tagline": "Encryption, privacy, and our threat model",
    "color": "#6610f2"
  },
  "id": 4
}

replaceOneById

Full replacement — all writable fields must be provided. Unlike updateOneById, omitted fields are reset to defaults. Inline collections (hub_translation) are not accepted — use updateOneById to modify them.

HTTP

Request

PUT /v1/blog/hubs/1920438291700
Authorization: Bearer <token>
Content-Type: application/json

{
  "slug": "security",
  "title": "Security",
  "tagline": "Encryption, privacy, and the threat model",
  "icon": "bi-shield-lock",
  "color": "#6f42c1"
}

Response

{
  "id": "1920438291700",
  "slug": "security",
  "title": "Security",
  "tagline": "Encryption, privacy, and the threat model",
  "icon": "bi-shield-lock",
  "color": "#6f42c1"
}

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "blog.hubs.replaceOneById",
  "params": {
    "id": "1920438291700",
    "document": {
      "slug": "security",
      "title": "Security",
      "tagline": "Encryption, privacy, and the threat model",
      "icon": "bi-shield-lock",
      "color": "#6f42c1"
    }
  },
  "id": 5
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "id": "1920438291700",
    "slug": "security",
    "title": "Security",
    "tagline": "Encryption, privacy, and the threat model",
    "icon": "bi-shield-lock",
    "color": "#6f42c1"
  },
  "id": 5
}

update

Batch update multiple hubs in a single transaction (all-or-nothing). See Batch operations.

HTTP

Request

PATCH /v1/blog/hubs
Authorization: Bearer <token>
Content-Type: application/json

{
  "items": [
    { "id": "1920438291700", "document": { "color": "#6f42c1" } },
    { "id": "1920438291701", "document": { "color": "#0d6efd" } }
  ]
}

Response

{
  "affected": 2,
  "items": [
    { "id": "1920438291700", "color": "#6f42c1" },
    { "id": "1920438291701", "color": "#0d6efd" }
  ]
}

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "blog.hubs.update",
  "params": {
    "items": [
      { "id": "1920438291700", "document": { "color": "#6f42c1" } },
      { "id": "1920438291701", "document": { "color": "#0d6efd" } }
    ]
  },
  "id": 6
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "affected": 2,
    "items": [
      { "id": "1920438291700", "color": "#6f42c1" },
      { "id": "1920438291701", "color": "#0d6efd" }
    ]
  },
  "id": 6
}

deleteOneById

Delete a hub. Posts that belonged to this hub are kept but become uncategorized (hub_id becomes null).

HTTP

Request

DELETE /v1/blog/hubs/1920438291700
Authorization: Bearer <token>

Response

{ "id": "1920438291700", "title": "Security" }

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "blog.hubs.deleteOneById",
  "params": { "id": "1920438291700" },
  "id": 7
}

Response

{
  "jsonrpc": "2.0",
  "result": { "id": "1920438291700", "title": "Security" },
  "id": 7
}

delete

Batch delete hubs in a single transaction (all-or-nothing). See Batch operations.

HTTP

Request

DELETE /v1/blog/hubs
Authorization: Bearer <token>
Content-Type: application/json

{
  "items": [
    { "id": "1920438291700" },
    { "id": "1920438291701" }
  ]
}

Response

{
  "affected": 2,
  "items": [
    { "id": "1920438291700" },
    { "id": "1920438291701" }
  ]
}

WebSocket

Request

{
  "jsonrpc": "2.0",
  "method": "blog.hubs.delete",
  "params": {
    "items": [
      { "id": "1920438291700" },
      { "id": "1920438291701" }
    ]
  },
  "id": 8
}

Response

{
  "jsonrpc": "2.0",
  "result": {
    "affected": 2,
    "items": [
      { "id": "1920438291700" },
      { "id": "1920438291701" }
    ]
  },
  "id": 8
}