misarblog

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 11 Imported by: 0

README

Misar.Blog Go SDK

Official Go client for the Misar.Blog developer API. Context-aware, typed responses, retry with back-off.

Install

go get github.com/Misar-AI/misarblog-sdks/go

Quick start

package main

import (
	"context"
	"errors"
	"fmt"

	misarblog "github.com/Misar-AI/misarblog-sdks/go"
)

func main() {
	blog := misarblog.New("mbk_...")
	ctx := context.Background()

	me, err := blog.Me.Get(ctx)
	if err != nil {
		panic(err)
	}

	thread, err := blog.Comments.List(ctx, "article-id", 50, 0)
	if err != nil {
		panic(err)
	}
	fmt.Println(me.Username, thread.TotalCount)

	if _, err := blog.AI.Complete(ctx, &misarblog.CompleteRequest{Prompt: "Draft an intro"}); err != nil {
		var limit *misarblog.PlanLimitError
		if errors.As(err, &limit) {
			fmt.Printf("%s plan is out of credits — upgrade at %s\n", limit.Plan, limit.UpgradeURL)
		}
	}
}

Authentication and plan gating

Every call goes through the metered gateway at https://api.misar.io/blog/v1 with your developer key as a Bearer token. Mint a key in the dashboard at https://www.misar.blog/dashboard/settings/api — key management is a cookie-session flow and is deliberately not exposed by this SDK.

Feature access and throughput follow the subscription attached to that key:

Signal Meaning
401 Missing, expired or revoked key
403 The key is scoped and lacks the scope this route needs
429 (plain) Rate limit — 100 requests/minute per key. The SDK retries with back-off
429 + plan_limit_exceeded A metered allowance is spent. Retrying will not help until it resets
402 + plan_limit_exceeded The feature is not on this plan

The last two raise *PlanLimitError rather than a generic error, carrying the plan slug, the pricing URL and (when the API supplies it) seconds until reset. Show the upgrade URL instead of reporting a bare failure — the SDK does not retry these, because retrying cannot change the outcome.

Covered operations

All 25 key-authenticated operations:

Group Operations
Articles list, get, create, update, create draft, search, recommendations
Series list, create, add article
Reactions get, add, remove
Comments list
Follows status
AI complete, titles
Images generate, upload
Account profile, plan, trial status, start trial
Analytics summary, upsell funnel

The API exposes no SSE or WebSocket endpoint that accepts an API key, so this SDK is request/response only. See openapi/blog.openapi.json for the machine-readable contract.

License

MIT — see LICENSE.

Documentation

Overview

Package misarblog is the official Go client for the Misar.Blog developer API.

Base URL is https://api.misar.io/blog/v1 — the blog gateway strips /api, so request paths never carry an /api prefix. Authenticate with a developer key (mbk_...) or an OAuth 2.1 access token, sent as a Bearer token.

blog := misarblog.New("mbk_...")
me, err := blog.Me.Get(context.Background())

Index

Constants

View Source
const EMBED_BASE = "https://misar.blog"

Variables

This section is empty.

Functions

func EmbedURL

func EmbedURL(username, slug, theme string) string

Types

type AIResource

type AIResource struct {
	// contains filtered or unexported fields
}

func (*AIResource) Complete

func (*AIResource) Titles

func (r *AIResource) Titles(ctx context.Context, req *TitlesRequest) (*TitlesResponse, error)

type APIError

type APIError struct {
	Status        int      `json:"-"`
	Message       string   `json:"error"`
	RequiredScope string   `json:"required_scope,omitempty"` // present on 403 scope failures
	GrantedScopes []string `json:"granted_scopes,omitempty"` // present on 403 scope failures
}

APIError is returned when the Misar.Blog API responds with a non-2xx status.

func (*APIError) Error

func (e *APIError) Error() string

type AddToSeriesRequest

type AddToSeriesRequest struct {
	ArticleSlug string `json:"article_slug"`
	Position    *int   `json:"position,omitempty"`
}

type AnalyticsResource

type AnalyticsResource struct {
	// contains filtered or unexported fields
}

func (*AnalyticsResource) Summary

func (r *AnalyticsResource) Summary(ctx context.Context, days int) (*AnalyticsSummary, error)

type AnalyticsSummary

type AnalyticsSummary struct {
	PeriodDays        int `json:"period_days"`
	Views             int `json:"views"`
	RevenueCents      int `json:"revenue_cents"`
	RevenueNetCents   int `json:"revenue_net_cents"`
	ActiveSubscribers int `json:"active_subscribers"`
}

type Article

type Article struct {
	ArticleSummary
	ContentMarkdown *string `json:"content_markdown"`
	ContentHTML     *string `json:"content_html"`
	Visibility      string  `json:"visibility"`
	UpdatedAt       *string `json:"updated_at"`
	ReadCount       int     `json:"read_count"`
	FeaturedImage   *string `json:"featured_image_url"`
	EditorURL       string  `json:"editor_url"`
}

type ArticleListResult

type ArticleListResult struct {
	Articles []ArticleSummary `json:"articles"`
	Total    int              `json:"total"`
}

type ArticleReactions

type ArticleReactions struct {
	ArticleID     string         `json:"article_id"`
	Counts        ReactionCounts `json:"counts"`
	Total         int            `json:"total"`
	UserReactions []string       `json:"user_reactions"`
}

type ArticleSummary

type ArticleSummary struct {
	ID          string   `json:"id"`
	Slug        string   `json:"slug"`
	Title       string   `json:"title"`
	Excerpt     *string  `json:"excerpt"`
	Status      string   `json:"status"`
	Tags        []string `json:"tags"`
	PublishedAt *string  `json:"published_at"`
	CreatedAt   string   `json:"created_at"`
	ViewCount   int      `json:"view_count"`
	IsPremium   bool     `json:"is_premium"`
	PriceCents  int      `json:"price_cents"`
	URL         string   `json:"url"`
}

type ArticlesResource

type ArticlesResource struct {
	// contains filtered or unexported fields
}

func (*ArticlesResource) CreateDraft

func (*ArticlesResource) Get

func (r *ArticlesResource) Get(ctx context.Context, slug string) (*Article, error)

func (*ArticlesResource) List

func (*ArticlesResource) Publish

func (*ArticlesResource) Recommendations

func (r *ArticlesResource) Recommendations(ctx context.Context, articleID string, limit int) (*RecommendationsResult, error)

func (*ArticlesResource) Search

func (*ArticlesResource) Update

type Client

type Client struct {
	Articles     *ArticlesResource
	AI           *AIResource
	Images       *ImagesResource
	Analytics    *AnalyticsResource
	Me           *MeResource
	Plan         *PlanResource
	Reactions    *ReactionsResource
	Series       *SeriesResource
	Comments     *CommentsResource
	Follows      *FollowsResource
	Trial        *TrialResource
	UpsellFunnel *UpsellFunnelResource
	// contains filtered or unexported fields
}

func New

func New(apiKey string, opts ...Option) *Client

New creates a Misar.Blog API client.

type Comment

type Comment struct {
	ID         string        `json:"id"`
	ArticleID  string        `json:"article_id"`
	UserID     string        `json:"user_id"`
	ParentID   *string       `json:"parent_id"`
	Content    string        `json:"content"`
	IsEdited   bool          `json:"is_edited"`
	IsHidden   bool          `json:"is_hidden"`
	ReplyCount int           `json:"reply_count"`
	CreatedAt  string        `json:"created_at"`
	UpdatedAt  string        `json:"updated_at"`
	User       CommentAuthor `json:"user"`
	// Replies is nested one level deep; nil on reply objects themselves.
	Replies []Comment `json:"replies,omitempty"`
}

type CommentAuthor

type CommentAuthor struct {
	ID          string  `json:"id"`
	Username    string  `json:"username"`
	DisplayName *string `json:"display_name"`
	AvatarURL   *string `json:"avatar_url"`
}

type CommentsResource

type CommentsResource struct {
	// contains filtered or unexported fields
}

func (*CommentsResource) List

func (r *CommentsResource) List(ctx context.Context, articleID string, limit, offset int) (*CommentsResult, error)

List returns an article's comment thread, newest first, with replies nested one level deep. limit defaults to 20 (max 100) and offset to 0 when zero.

type CommentsResult

type CommentsResult struct {
	Comments   []Comment `json:"comments"`
	TotalCount int       `json:"totalCount"`
	HasMore    bool      `json:"hasMore"`
}

type CompleteRequest

type CompleteRequest struct {
	Prompt    string `json:"prompt"`
	System    string `json:"system,omitempty"`
	MaxTokens int    `json:"max_tokens,omitempty"`
}

type CompletionResponse

type CompletionResponse struct {
	Text   string `json:"text"`
	Tokens int    `json:"tokens,omitempty"`
}

type CreateDraftRequest

type CreateDraftRequest struct {
	Title        string   `json:"title"`
	BodyMarkdown string   `json:"body_markdown"`
	Tags         []string `json:"tags,omitempty"`
}

type CreateSeriesRequest

type CreateSeriesRequest struct {
	Title       string `json:"title"`
	Description string `json:"description,omitempty"`
}

type FollowStatus

type FollowStatus struct {
	IsFollowing    bool `json:"isFollowing"`
	FollowerCount  int  `json:"followerCount"`
	FollowingCount int  `json:"followingCount"`
}

type FollowsResource

type FollowsResource struct {
	// contains filtered or unexported fields
}

func (*FollowsResource) Status

func (r *FollowsResource) Status(ctx context.Context, userID string) (*FollowStatus, error)

Status returns a profile's follower/following counts plus whether the key's owner follows it.

type GenerateImageRequest

type GenerateImageRequest struct {
	Prompt string `json:"prompt"`
	Size   string `json:"size,omitempty"` // 1024x1024|1792x1024|1024x1792
}

type GeneratedImage

type GeneratedImage struct {
	URL  string `json:"url"`
	Size string `json:"size,omitempty"`
}

type ImagesResource

type ImagesResource struct {
	// contains filtered or unexported fields
}

func (*ImagesResource) Generate

func (*ImagesResource) Upload

func (r *ImagesResource) Upload(ctx context.Context, filename string, data []byte) (map[string]any, error)

Upload sends a file to the CDN as multipart/form-data under the "file" field.

type ListArticlesParams

type ListArticlesParams struct {
	Status      string // draft|published|scheduled|archived|flagged|all
	Visibility  string // public|subscribers|paid|private|webhook_only
	WebhookOnly *bool
	Sort        string // newest|views
	Limit       int
}

ListArticlesParams are the optional filters for ArticlesResource.List.

type MeResource

type MeResource struct {
	// contains filtered or unexported fields
}

func (*MeResource) Get

func (r *MeResource) Get(ctx context.Context) (*Profile, error)

type MutateArticleResult

type MutateArticleResult struct {
	ID          string  `json:"id"`
	Slug        string  `json:"slug"`
	Title       string  `json:"title"`
	Status      string  `json:"status"`
	URL         string  `json:"url,omitempty"`
	EditorURL   string  `json:"editor_url,omitempty"`
	PublishedAt *string `json:"published_at,omitempty"`
	CreatedAt   string  `json:"created_at,omitempty"`
}

type NetworkError

type NetworkError struct {
	Message string
	Cause   error
}

NetworkError wraps a transport-level failure (the request never reached the API).

func (*NetworkError) Error

func (e *NetworkError) Error() string

func (*NetworkError) Unwrap

func (e *NetworkError) Unwrap() error

type Option

type Option func(*Client)

func WithBaseURL

func WithBaseURL(u string) Option

WithBaseURL overrides the API base (default https://api.misar.io/blog/v1).

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient injects a custom *http.Client (used by tests).

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets the total number of attempts for 429/5xx and transport errors.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-request timeout.

type Plan

type Plan struct {
	Plan       string         `json:"plan"`
	Status     string         `json:"status"`
	Quota      map[string]any `json:"quota"`
	UpgradeURL string         `json:"upgrade_url"`
}

type PlanLimitError

type PlanLimitError struct {
	Status int
	// Message is the human-readable headline plus call-to-action.
	Message string
	// Plan is the account's current plan slug.
	Plan string
	// UpgradeURL points at the pricing page for this account.
	UpgradeURL string
	// RetryAfter is seconds until the allowance resets, 0 when not supplied.
	RetryAfter int
	// Upgrade is the full upgrade offer from the response body.
	Upgrade map[string]any
}

PlanLimitError is returned when the subscription attached to the API key blocks the call. The API signals this with code "plan_limit_exceeded" and answers 429 when a metered allowance is exhausted (retryable once the period rolls over) or 402 when the feature is locked outright.

It is surfaced as a distinct type rather than a generic 429 because retrying cannot help until the allowance resets or the plan changes — the SDK stops retrying as soon as it sees this code.

func (*PlanLimitError) Error

func (e *PlanLimitError) Error() string

type PlanResource

type PlanResource struct {
	// contains filtered or unexported fields
}

func (*PlanResource) Get

func (r *PlanResource) Get(ctx context.Context) (*Plan, error)

type Profile

type Profile struct {
	ID          string  `json:"id"`
	Username    string  `json:"username"`
	DisplayName string  `json:"display_name"`
	Bio         *string `json:"bio"`
	AvatarURL   *string `json:"avatar_url"`
	URL         string  `json:"url"`
}

type PublishArticleRequest

type PublishArticleRequest struct {
	Title         string   `json:"title"`
	BodyMarkdown  string   `json:"body_markdown"`
	Tags          []string `json:"tags,omitempty"`
	CoverImageURL string   `json:"cover_image_url,omitempty"`
	ScheduleAt    string   `json:"schedule_at,omitempty"`
	Visibility    string   `json:"visibility,omitempty"`
}

PublishArticleRequest is the body for ArticlesResource.Publish.

type ReactionCounts

type ReactionCounts struct {
	Like     int `json:"like"`
	Clap     int `json:"clap"`
	Bookmark int `json:"bookmark"`
}

type ReactionMutationResult

type ReactionMutationResult struct {
	Success bool `json:"success"`
	Reacted bool `json:"reacted"`
	Toggled bool `json:"toggled,omitempty"`
}

type ReactionsResource

type ReactionsResource struct {
	// contains filtered or unexported fields
}

func (*ReactionsResource) Add

func (r *ReactionsResource) Add(ctx context.Context, articleID, reactionType string) (*ReactionMutationResult, error)

func (*ReactionsResource) Get

func (r *ReactionsResource) Get(ctx context.Context, articleID string) (*ArticleReactions, error)

func (*ReactionsResource) Remove

func (r *ReactionsResource) Remove(ctx context.Context, articleID, reactionType string) (*ReactionMutationResult, error)

type RecommendationsResult

type RecommendationsResult struct {
	Recommendations []map[string]any `json:"recommendations"`
}

type SearchParams

type SearchParams struct {
	Q      string
	Type   string // all|articles|profiles|tags
	Tag    string
	Author string
	Sort   string // relevance|newest|oldest|popular
	From   string
	To     string
	Limit  int
}

type SearchResult

type SearchResult struct {
	Articles []map[string]any `json:"articles"`
	Profiles []map[string]any `json:"profiles"`
	Tags     []map[string]any `json:"tags"`
}

type Series

type Series struct {
	ID            string  `json:"id"`
	Slug          string  `json:"slug"`
	Title         string  `json:"title"`
	Description   *string `json:"description"`
	CoverImageURL *string `json:"cover_image_url"`
	Visibility    string  `json:"visibility"`
	CreatedAt     string  `json:"created_at"`
	URL           string  `json:"url"`
}

type SeriesListResult

type SeriesListResult struct {
	Series []Series `json:"series"`
}

type SeriesResource

type SeriesResource struct {
	// contains filtered or unexported fields
}

func (*SeriesResource) AddArticle

func (r *SeriesResource) AddArticle(ctx context.Context, slug string, req *AddToSeriesRequest) (map[string]any, error)

func (*SeriesResource) Create

func (r *SeriesResource) Create(ctx context.Context, req *CreateSeriesRequest) (*Series, error)

func (*SeriesResource) List

type StartTrialRequest

type StartTrialRequest struct {
	Feature string `json:"feature,omitempty"`
	Ref     string `json:"ref,omitempty"`
}

type TitleResult

type TitleResult struct {
	Title string `json:"title"`
	Hint  string `json:"hint"`
}

type TitlesRequest

type TitlesRequest struct {
	Action  string `json:"action"` // suggest|seo
	Prompt  string `json:"prompt,omitempty"`
	Context string `json:"context,omitempty"`
}

type TitlesResponse

type TitlesResponse struct {
	Titles []TitleResult `json:"titles"`
	Raw    string        `json:"raw"`
}

type TokenResult

type TokenResult struct {
	Token     string `json:"token"`
	ExpiresAt int64  `json:"expiresAt"`
}

func RefreshToken

func RefreshToken(token, baseURL string) (*TokenResult, error)

type TrialResource

type TrialResource struct {
	// contains filtered or unexported fields
}

func (*TrialResource) Start

func (r *TrialResource) Start(ctx context.Context, req *StartTrialRequest) (map[string]any, error)

func (*TrialResource) Status

func (r *TrialResource) Status(ctx context.Context) (*TrialStatus, error)

type TrialStatus

type TrialStatus struct {
	Eligible  bool    `json:"eligible"`
	Active    bool    `json:"active"`
	StartedAt *string `json:"started_at"`
	EndsAt    *string `json:"ends_at"`
}

type UpdateArticleRequest

type UpdateArticleRequest struct {
	Title        *string   `json:"title,omitempty"`
	BodyMarkdown *string   `json:"body_markdown,omitempty"`
	Tags         *[]string `json:"tags,omitempty"`
	Publish      *bool     `json:"publish,omitempty"`
}

UpdateArticleRequest is the body for ArticlesResource.Update. Use pointers so zero values are distinguishable from "unset".

type UpsellFunnelParams

type UpsellFunnelParams struct {
	Days    int
	Feature string
}

type UpsellFunnelResource

type UpsellFunnelResource struct {
	// contains filtered or unexported fields
}

func (*UpsellFunnelResource) Get

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL