api

package
v0.1.74 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package api is the (thin) HTTP client for the Civitai Apps surface.

Auth/submit contract (civitai/civitai PR #2644):

  • The token-authenticated bundle-upload route is POST /api/v1/blocks/submit-version (Bearer access token). This is the default the CLI targets; CIVITAI_SUBMIT_PATH overrides it.
  • The legacy /api/blocks/submit-version route is session-cookie + moderator authenticated and not used by the CLI.

Auth is supplied via a TokenSource so OAuth (device-flow) access tokens are refreshed transparently before a request and once on a 401; a personal API key is a static source with no refresh. Keeping the network call behind the Submitter interface makes the whole thing testable without a live server.

Index

Constants

View Source
const (
	ScopeUserRead           = 1 << 0
	ScopeUserWrite          = 1 << 1
	ScopeModelsRead         = 1 << 2
	ScopeModelsWrite        = 1 << 3
	ScopeModelsDelete       = 1 << 4
	ScopeMediaRead          = 1 << 5
	ScopeMediaWrite         = 1 << 6
	ScopeMediaDelete        = 1 << 7
	ScopeArticlesRead       = 1 << 8
	ScopeArticlesWrite      = 1 << 9
	ScopeArticlesDelete     = 1 << 10
	ScopeBountiesRead       = 1 << 11
	ScopeBountiesWrite      = 1 << 12
	ScopeBountiesDelete     = 1 << 13
	ScopeAIServicesRead     = 1 << 14
	ScopeAIServicesWrite    = 1 << 15 // spend Buzz on AI services (generation)
	ScopeBuzzRead           = 1 << 16 // read the user's Buzz balance
	ScopeCollectionsRead    = 1 << 17
	ScopeCollectionsWrite   = 1 << 18
	ScopeSocialWrite        = 1 << 19
	ScopeSocialTip          = 1 << 20
	ScopeNotificationsRead  = 1 << 21
	ScopeNotificationsWrite = 1 << 22
	ScopeVaultRead          = 1 << 23
	ScopeVaultWrite         = 1 << 24
	ScopeAppBlocksSubmit    = 1 << 25
	// ScopeFull is the OR of bits 0..24 — every scope a personal key carries. It
	// EXCLUDES AppBlocksSubmit (1<<25), matching the upstream Full constant
	// (1<<25)-1.
	ScopeFull = (1 << 25) - 1
)

Token-scope bits, mirrored from @civitai/auth token-scope (civitai/civitai src/shared/constants/token-scope.constants.ts). These are STABLE/frozen bit positions in the tokenScope bitmask GET /api/v1/me returns.

View Source
const (
	StartDevTunnelPath = "/api/trpc/blocks.startDevTunnel"
	StopDevTunnelPath  = "/api/trpc/blocks.stopDevTunnel"
)

StartDevTunnelPath / StopDevTunnelPath are the non-batched tRPC routes.

View Source
const (
	// ClientID is the public OAuth client id for the CLI.
	ClientID = "civitai-cli"

	// DeviceScope is UserRead|AppBlocksSubmit|AppBlocksDevTunnel (bit flags), the
	// fixed scope the CLI requests on login. 100663297 == (1<<0)|(1<<25)|(1<<26):
	//   - UserRead           (1<<0  = 1)        — whoami / identity.
	//   - AppBlocksSubmit     (1<<25 = 33554432) — `app submit` AND the dev-token mint
	//     gate (both require this on an OAuth token).
	//   - AppBlocksDevTunnel  (1<<26 = 67108864) — `app dev-tunnel` (start/stop/status).
	//     The dev-tunnel tRPC procs require this bit on an OAuth token; without it a
	//     login token 403s the scope gate and only a Full personal API key works.
	// This is exactly the civitai-cli OauthClient.allowedScopes set (100663297). We
	// deliberately do NOT request AIServicesWrite: the server's device-flow
	// validateScope is all-or-nothing — requesting any scope the client doesn't
	// allow REJECTS the whole login — and we don't want every login token to carry
	// general Buzz-spend authority.
	//
	// 🔴 SERVER DEPENDENCY: AppBlocksDevTunnel (bit 26) + the widened civitai-cli
	// allowedScopes (100663297) must be LIVE on prod auth (migration applied) BEFORE
	// this ships — else the device request exceeds allowedScopes and login 400s
	// (invalid_scope). Do NOT release this ahead of the civitai server change.
	//
	// What a login token can do: MINT an App-Blocks dev token (the mint gate needs
	// AppBlocksSubmit), open an on-site dev tunnel (AppBlocksDevTunnel), and drive
	// the read/estimate harness paths — cost preview / whatif, catalog browsing, app
	// storage. It CANNOT run a real generation: the dev-token mint applies a uniform
	// AIServicesWrite ceiling on the budgeted-spend scope, so a login-minted dev
	// token has ai:write:budgeted STRIPPED (read/estimate only). Real-Buzz dev:live
	// needs a personal API key with full scope (civitai.com/user/account), which
	// carries AIServicesWrite.
	DeviceScope = "100663297"
)

OAuth device-authorization-grant client for the civitai-cli public client.

Contract: the OAuth provider lives on a dedicated auth origin (production: auth.civitai.com) discovered via OpenID well-known metadata. civitai.com itself does NOT serve the OAuth endpoints or the discovery document — it 404s to the SPA. The endpoints (resolved from the discovery doc) are:

  • device init: POST {issuer}/api/auth/oauth/device (device_authorization_endpoint)
  • device poll: POST {issuer}/api/auth/oauth/device-token (issuer + pathDeviceToken; not in the doc)
  • token refresh: POST {issuer}/api/auth/oauth/token (token_endpoint)

ALL THREE are application/x-www-form-urlencoded AND must carry an Origin: {issuer} header — the auth host enforces a host-wide same-origin guard on form POSTs (origin-less/cross-site form POST -> 403; a JSON body -> 400 "Missing client_id"). See resolveEndpoints / postForm.

civitai-cli is a PUBLIC client (PKCE/device): no client secret.

View Source
const BuzzAccountPath = "/api/trpc/buzz.getBuzzAccount"

BuzzAccountPath is the tRPC route that returns the spendable Buzz balance.

View Source
const CloneInfoPath = "/api/trpc/blocks.getMyForgejoCloneInfo"

CloneInfoPath is the tRPC query that returns the caller's per-user Forgejo clone info for one of THEIR apps (owner-only, App-Blocks-flag-gated). Backs `civitai app pull`. The token is embedded in CloneURL (HTTP-Basic) — caller must treat it as a secret (see the leakage caveat in `civitai app pull`).

View Source
const DefaultSubmitPath = "/api/v1/blocks/submit-version"

DefaultSubmitPath is the token-authenticated submit-version route.

View Source
const DevTokenPath = "/api/v1/blocks/dev-token"

DevTokenPath is the invite-gated route that mints a short-lived dev block token for `npm run dev:live` (POST {"slug": ..., "scopes"?: [...]}; civitai/civitai src/pages/api/v1/blocks/dev-token.ts). 200 { token, ... } on success; a PENDING (un-approved) slug is accepted, and a slug with NO app row yet mints from the request-body `scopes` (the dev's LOCAL manifest scopes, clamped server-side) — so `create → dev-token → dev:live` works with no submit step. Error bodies are {message}: 404 slug registered to a different account / genuinely not found, 403 not-invited/insufficient-scope, 429 rate-limited, 503 flag-off. The minted token's CAPABILITIES depend on the bearer: a full-scope personal API key mints a spend-capable token; an OAuth (`civitai login`) credential mints a read-only one.

View Source
const ModelFileType = "Model"

ModelFileType is the `type` value of the main model-weights file. A file whose type is anything else (e.g. "Archive", "Training Data", "Config", "VAE") is not weights but a different deliverable — the renderers tag it with an informational type marker; `download` fetches any type regardless.

View Source
const SubmissionsPath = "/api/v1/blocks/submissions"

SubmissionsPath is the token-authenticated, self-scoped submission-status route (GET; civitai/civitai src/pages/api/v1/blocks/submissions.ts).

View Source
const WithdrawPath = "/api/v1/blocks/withdraw"

WithdrawPath is the token-authenticated, self-scoped withdraw route (POST {"publishRequestId": ...}; civitai/civitai src/pages/api/v1/blocks/withdraw.ts). 200 on success (incl. an idempotent already-withdrawn), 404 not-found-or-not-yours, 409 not in a withdrawable (pending) state.

Variables

View Source
var ErrBuzzScope = fmt.Errorf("credential lacks the Buzz-read scope")

ErrBuzzScope is returned by GetBuzzAccount when the stored credential lacks the Buzz-read scope (the server answers 403 FORBIDDEN). The command layer maps this to actionable, personal-key guidance.

View Source
var ErrNoRefresh = fmt.Errorf("token source cannot refresh")

ErrNoRefresh is returned by a non-refreshable (personal-key) TokenSource.

View Source
var ErrSlugRegisteredToOtherAccount = errors.New("slug is registered to a different account")

ErrSlugRegisteredToOtherAccount is wrapped by MintDevToken's error when the dev-token route 404s with the bare "App not found" — the server's anti-shadow guard: the requested slug is an APPROVED app owned by a DIFFERENT account, so the no-row local-manifest mint path is refused. It is the ONLY rename-retriable 404 (the caller can pick a new, free slug and retry). Other 404s (e.g. an owned-but-not-yet-deployed app, which carries a "no live deployment" message) are NOT retriable and do NOT wrap this sentinel. Callers branch with errors.Is(err, ErrSlugRegisteredToOtherAccount) rather than matching strings.

Functions

func DownloadNeedsAuth added in v0.1.64

func DownloadNeedsAuth(fileURL, baseURL string) bool

DownloadNeedsAuth reports whether a bearer token would be attached when downloading fileURL (i.e. it targets a trusted Civitai/base host, so the CLI authenticates the request). It backs the `download --dry-run` plan's "authentication" line. An off-domain signed-storage redirect target reports false — it needs no token — but the initial Civitai download route reports true, which is what the user sees before the transfer.

Types

type ArticleDetail added in v0.1.62

type ArticleDetail struct {
	ID          int                 `json:"id"`
	Title       string              `json:"title"`
	NSFWLevel   int                 `json:"nsfwLevel"`
	PublishedAt string              `json:"publishedAt"`
	User        *ArticleUser        `json:"user"`
	Tags        []ArticleTag        `json:"tags"`
	Stats       *ArticleDetailStats `json:"stats"`
	// Content is the article body as sanitized HTML (the TipTap/ProseMirror
	// output). `articles get --content` renders it to readable plain text /
	// lightweight markdown; --json still returns the raw body untouched.
	Content string `json:"content"`
}

ArticleDetail is the subset of `GET /api/v1/articles/{id}` the CLI renders. The endpoint also returns the full article body (content/contentJson), attachments, and coverImage — all preserved for --json via the raw body.

type ArticleDetailStats added in v0.1.62

type ArticleDetailStats struct {
	ViewCountAllTime      int `json:"viewCountAllTime"`
	CommentCountAllTime   int `json:"commentCountAllTime"`
	LikeCountAllTime      int `json:"likeCountAllTime"`
	HeartCountAllTime     int `json:"heartCountAllTime"`
	FavoriteCountAllTime  int `json:"favoriteCountAllTime"`
	CollectedCountAllTime int `json:"collectedCountAllTime"`
}

ArticleDetailStats is the stats block `GET /api/v1/articles/{id}` embeds. The detail endpoint returns AllTime-suffixed counters (from the ArticleStat table) — distinct from the list endpoint's ArticleStats keys.

type ArticleListItem added in v0.1.62

type ArticleListItem struct {
	ID          int          `json:"id"`
	Title       string       `json:"title"`
	NSFWLevel   int          `json:"nsfwLevel"`
	PublishedAt string       `json:"publishedAt"`
	User        *ArticleUser `json:"user"`
	Tags        []ArticleTag `json:"tags"`
	Stats       ArticleStats `json:"stats"`
}

ArticleListItem is the subset of a `GET /api/v1/articles` item the CLI renders in its compact list. The full item (coverImage, cosmetics, content flags) is preserved for --json via the result's Raw bytes.

type ArticleSearchResult added in v0.1.62

type ArticleSearchResult struct {
	Items    []ArticleListItem `json:"items"`
	Metadata Metadata          `json:"metadata"`
	Raw      []byte            `json:"-"`
}

ArticleSearchResult bundles the parsed items + pagination metadata with the raw response body (for --json passthrough).

type ArticleStats added in v0.1.62

type ArticleStats struct {
	FavoriteCount  int `json:"favoriteCount"`
	CollectedCount int `json:"collectedCount"`
	CommentCount   int `json:"commentCount"`
	LikeCount      int `json:"likeCount"`
	ViewCount      int `json:"viewCount"`
}

ArticleStats is the per-article stats block the LIST endpoint embeds (`GET /api/v1/articles`). Note the detail endpoint uses different, AllTime- suffixed keys — see ArticleDetailStats.

type ArticleTag added in v0.1.62

type ArticleTag struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

ArticleTag is a tag attached to an article (`tags[]` on both list + detail).

type ArticleUser added in v0.1.62

type ArticleUser struct {
	ID       int    `json:"id"`
	Username string `json:"username"`
}

ArticleUser is the minimal author view the article endpoints embed. The list and detail payloads both nest the author under `user` (the models endpoints use `creator` instead); only id + username are rendered, the rest (image, cosmetics, profilePicture) is preserved for --json via the raw body.

type BuzzAccount added in v0.1.9

type BuzzAccount struct {
	Blue   int64 `json:"blue"`
	Green  int64 `json:"green"`
	Yellow int64 `json:"yellow"`
}

BuzzAccount is the spendable Buzz balance from buzz.getBuzzAccount.

func (*BuzzAccount) Total added in v0.1.38

func (a *BuzzAccount) Total() int64

Total is the sum of the blue, green, and yellow balances.

type BuzzReader added in v0.1.9

type BuzzReader interface {
	// GetBuzzAccount returns the caller's Buzz balance. A credential lacking the
	// Buzz-read scope yields ErrBuzzScope (the server answers 403).
	GetBuzzAccount(ctx context.Context) (*BuzzAccount, error)
}

BuzzReader reads the caller's spendable Buzz balance.

type Client

type Client struct {
	BaseURL    string
	Tokens     TokenSource
	SubmitPath string // route for submit-version; CIVITAI_SUBMIT_PATH overrides
	HTTP       *http.Client
	// SubmitTimeout overrides the submit-upload timeout when non-zero; it
	// defaults to submitTimeout. Used by tests to exercise the timeout-recovery
	// path without a real slow upload.
	SubmitTimeout time.Duration
	// SubmitPollDelay overrides the inter-attempt delay of the post-timeout
	// recovery poll when set (>= 0 with the zero value meaning "use the
	// default"); tests set it to 0 to avoid sleeping.
	SubmitPollDelay *time.Duration
	// RetryBackoffBase overrides the base delay of the transient-failure retry
	// on read GETs (see retry.go). A nil pointer means "use defaultRetryBackoff";
	// tests set it to a pointer-to-0 for instant, sleepless retries.
	RetryBackoffBase *time.Duration
	// Stderr receives the one-line transient-retry notices on read GETs; nil
	// defaults to os.Stderr. Tests point it at a buffer to assert the notice.
	Stderr io.Writer
	// MaxResponseBody overrides the per-response body read cap (see
	// maxResponseBody) when > 0. It bounds memory against a pathological body;
	// a real civitai JSON page is far below the default. Tests set a small value
	// to exercise the over-cap guard without allocating 64 MiB.
	MaxResponseBody int64
	// AllowPrivateDownloadHosts disables the download SSRF guard — the https-only
	// requirement AND the internal-range dial block (loopback/link-local/private/
	// ULA) enforced by downloadHTTPClient. It defaults to FALSE (production-safe):
	// a server-supplied downloadUrl (or redirect) that is plain-http or resolves
	// to a non-public IP is refused. ONLY the download tests set it true, because
	// their httptest servers bind plain-http loopback (127.0.0.1), which the guard
	// would otherwise block. Never set it in production code.
	AllowPrivateDownloadHosts bool
}

Client is the default HTTP implementation.

func New

func New(baseURL, token, submitPath string) *Client

New builds a Client with sane defaults from a static token (personal API key or a one-shot access token). For refreshable OAuth credentials use NewWithSource.

func NewWithSource

func NewWithSource(baseURL string, src TokenSource, submitPath string) *Client

NewWithSource builds a Client backed by a TokenSource (which may refresh).

func (*Client) DownloadFile added in v0.1.63

func (c *Client) DownloadFile(ctx context.Context, fileURL string) (*http.Response, error)

DownloadFile implements Downloader. See the interface doc for the contract.

func (*Client) GetArticle added in v0.1.62

func (c *Client) GetArticle(ctx context.Context, id string) (*ArticleDetail, []byte, error)

GetArticle calls GET /api/v1/articles/{id}. Returns the parsed detail and the raw body (for --json).

func (*Client) GetBuzzAccount added in v0.1.9

func (c *Client) GetBuzzAccount(ctx context.Context) (*BuzzAccount, error)

GetBuzzAccount reads the caller's spendable Buzz balance via the buzz.getBuzzAccount tRPC route, refreshing the OAuth access token if needed. On 200 it returns the {blue,green,yellow} balance; on a 403 (the credential lacks the Buzz-read scope) it returns ErrBuzzScope so the command layer can print the personal-key guidance. The tRPC success envelope is {"result":{"data":{"json":{...}}}}.

func (*Client) GetCollection added in v0.1.62

func (c *Client) GetCollection(ctx context.Context, id string) (*CollectionDetail, []byte, error)

GetCollection calls GET /api/v1/collections/{id}. Returns the parsed detail and the raw body (for --json).

func (*Client) GetForgejoCloneInfo added in v0.1.38

func (c *Client) GetForgejoCloneInfo(ctx context.Context, app string) (*ForgejoCloneInfo, error)

GetForgejoCloneInfo calls the owner-only getMyForgejoCloneInfo tRPC query for the given app (a slug — the repo name — or an appBlockId). It lazily provisions the caller's scoped Forgejo identity server-side and returns the tokened clone URL the `pull` command hands to git.

func (*Client) GetModel added in v0.1.62

func (c *Client) GetModel(ctx context.Context, id string) (*ModelDetail, []byte, error)

GetModel calls GET /api/v1/models/{id}. Returns the parsed detail and the raw body (for --json).

func (*Client) GetModelVersion added in v0.1.62

func (c *Client) GetModelVersion(ctx context.Context, id string) (*ModelVersionDetail, []byte, error)

GetModelVersion calls GET /api/v1/model-versions/{id}.

func (*Client) GetModelVersionByHash added in v0.1.62

func (c *Client) GetModelVersionByHash(ctx context.Context, hash string) (*ModelVersionDetail, []byte, error)

GetModelVersionByHash calls GET /api/v1/model-versions/by-hash/{hash}. The server upper-cases the hash server-side; any file-hash type (AutoV2, SHA256, …) is accepted.

func (*Client) GetSubmission added in v0.1.2

func (c *Client) GetSubmission(ctx context.Context, id, blockID string) (*Submission, error)

GetSubmission returns a single submission. Exactly one of id (a pubreq id) or blockID (an app slug) should be set; id takes precedence if both are given.

func (*Client) ListSubmissions added in v0.1.2

func (c *Client) ListSubmissions(ctx context.Context, blockID string) ([]Submission, error)

ListSubmissions returns the caller's own submissions (newest first). An empty blockID lists all; a non-empty blockID narrows to that app's submissions.

func (*Client) MintDevToken added in v0.1.15

func (c *Client) MintDevToken(ctx context.Context, slug string, scopes []string) (string, error)

MintDevToken mints a short-lived dev block token for the given app slug, returning the JWT from the response's .token field. scopes carries the caller's local manifest scopes for the server's no-row (no app registered yet) mint path; they are clamped server-side and omitted from the body when empty/nil (registered-app and read-only paths are unaffected). The OAuth access token is refreshed transparently on a 401. A non-2xx is mapped by devTokenError.

func (*Client) SearchArticles added in v0.1.62

func (c *Client) SearchArticles(ctx context.Context, q url.Values) (*ArticleSearchResult, error)

SearchArticles calls GET /api/v1/articles with the given query params.

func (*Client) SearchCollections added in v0.1.62

func (c *Client) SearchCollections(ctx context.Context, q url.Values) (*CollectionSearchResult, error)

SearchCollections calls GET /api/v1/collections with the given query params.

func (*Client) SearchCreators added in v0.1.62

func (c *Client) SearchCreators(ctx context.Context, q url.Values) (*CreatorSearchResult, error)

SearchCreators calls GET /api/v1/creators.

func (*Client) SearchImages added in v0.1.62

func (c *Client) SearchImages(ctx context.Context, q url.Values) (*ImageSearchResult, error)

SearchImages calls GET /api/v1/images with the given query params.

func (*Client) SearchModels added in v0.1.62

func (c *Client) SearchModels(ctx context.Context, q url.Values) (*ModelSearchResult, error)

SearchModels calls GET /api/v1/models with the given query params.

func (*Client) SearchTags added in v0.1.62

func (c *Client) SearchTags(ctx context.Context, q url.Values) (*TagSearchResult, error)

SearchTags calls GET /api/v1/tags.

func (*Client) SearchUsers added in v0.1.62

func (c *Client) SearchUsers(ctx context.Context, q url.Values) (*UserSearchResult, error)

SearchUsers calls GET /api/v1/users — the public user search. This is the only public users read route: the per-id `/api/v1/users/{userId}` route is an internal webhook (POST + system token) and is NOT usable by the CLI, so `civitai users get` resolves a user through this search (by ?query= for a name, or ?ids= for a numeric id).

func (*Client) StartDevTunnel added in v0.1.41

func (c *Client) StartDevTunnel(ctx context.Context, blockID, sshPublicKey string, declaredScopes []string) (*DevTunnelSession, error)

StartDevTunnel POSTs blocks.startDevTunnel and returns the minted session. The OAuth access token is refreshed transparently on a 401.

func (*Client) StopDevTunnel added in v0.1.41

func (c *Client) StopDevTunnel(ctx context.Context, sessionID, blockID string) (bool, error)

StopDevTunnel POSTs blocks.stopDevTunnel. A non-empty sessionID selects by session (preferred); otherwise blockID selects the caller's active tunnel for that app. Returns whether the server tore a session down.

func (*Client) SubmitVersion

func (c *Client) SubmitVersion(ctx context.Context, zipBytes []byte, slug, version string) (*SubmitResult, error)

SubmitVersion uploads the bundle to the token-authenticated submit route, refreshing the OAuth access token transparently if needed.

The upload can complete server-side while its HTTP response is slow or never arrives within the timeout — observed in the wild as a false "context deadline exceeded" failure on a submit that had actually landed, leaving the user to retry into "you already have a pending submission". So when (and only when) the POST fails with a timeout / deadline-exceeded / no-response error (as opposed to a clean HTTP error status), this polls GET /api/v1/blocks/submissions for a submission matching slug+version and, if one is now present, reports it as a success — surfacing the pubreq id. If no matching submission is found, it returns a clear error telling the user to check `civitai app status` before resubmitting.

func (*Client) WhoAmI

func (c *Client) WhoAmI(ctx context.Context) (*Identity, error)

WhoAmI verifies the token against /api/v1/me, refreshing the OAuth access token transparently if needed.

func (*Client) WithdrawRequest added in v0.1.8

func (c *Client) WithdrawRequest(ctx context.Context, publishRequestID string) error

WithdrawRequest withdraws the caller's own pending publish request. A 200 is success (the server is idempotent: already-withdrawn also returns 200). A non-2xx is mapped to an actionable error by withdrawError.

type CollectionDetail added in v0.1.62

type CollectionDetail struct {
	ID            int             `json:"id"`
	Name          string          `json:"name"`
	Description   string          `json:"description"`
	Type          string          `json:"type"`
	NSFWLevel     *int            `json:"nsfwLevel"`
	Read          string          `json:"read"`
	IsPublic      bool            `json:"isPublic"`
	CoverImageURL string          `json:"coverImageUrl"`
	User          *CollectionUser `json:"user"`
	Tags          []CollectionTag `json:"tags"`
}

CollectionDetail is the subset of `GET /api/v1/collections/{id}` the CLI renders. The detail shape drops itemCount but adds tags[].

type CollectionListItem added in v0.1.62

type CollectionListItem struct {
	ID            int             `json:"id"`
	Name          string          `json:"name"`
	Description   string          `json:"description"`
	Type          string          `json:"type"`
	NSFWLevel     *int            `json:"nsfwLevel"`
	Read          string          `json:"read"`
	IsPublic      bool            `json:"isPublic"`
	ItemCount     int             `json:"itemCount"`
	CoverImageURL string          `json:"coverImageUrl"`
	User          *CollectionUser `json:"user"`
}

CollectionListItem is a `GET /api/v1/collections` item. The endpoint projects a fixed public shape (no cross-user fields); coverImageUrl is a ready-to-use edge URL (or null). itemCount is the count of ACCEPTED items.

type CollectionSearchResult added in v0.1.62

type CollectionSearchResult struct {
	Items    []CollectionListItem `json:"items"`
	Metadata Metadata             `json:"metadata"`
	Raw      []byte               `json:"-"`
}

CollectionSearchResult bundles the parsed items + pagination metadata with the raw response body (for --json passthrough).

type CollectionTag added in v0.1.62

type CollectionTag struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

CollectionTag is a tag attached to a collection (detail-only `tags[]`).

type CollectionUser added in v0.1.62

type CollectionUser struct {
	ID       *int   `json:"id"`
	Username string `json:"username"`
}

CollectionUser is the minimal owner view the collection endpoints embed. Both id and username can be null server-side, so id is a pointer and username is left as its zero value ("") when absent.

type Creator added in v0.1.62

type Creator struct {
	Username string `json:"username"`
}

Creator is the minimal creator view the model endpoints embed.

type CreatorItem added in v0.1.62

type CreatorItem struct {
	Username   string `json:"username"`
	ModelCount int    `json:"modelCount"`
	Link       string `json:"link"`
}

CreatorItem is a `GET /api/v1/creators` item.

type CreatorSearchResult added in v0.1.62

type CreatorSearchResult struct {
	Items    []CreatorItem `json:"items"`
	Metadata Metadata      `json:"metadata"`
	Raw      []byte        `json:"-"`
}

CreatorSearchResult bundles parsed creators + metadata with the raw body.

type DevTokenMinter added in v0.1.15

type DevTokenMinter interface {
	// MintDevToken mints a dev block token for the given app slug and returns
	// the JWT. scopes carries the caller's LOCAL block.manifest.json scopes for
	// the server's no-row mint path (clamped server-side); pass nil/empty when
	// no manifest is available. A non-2xx is mapped by devTokenError.
	MintDevToken(ctx context.Context, slug string, scopes []string) (string, error)
}

DevTokenMinter mints a short-lived dev block token for `npm run dev:live`.

type DevTunnelController added in v0.1.41

type DevTunnelController interface {
	// StartDevTunnel mints a tunnel credential + host for blockId, binding it to
	// the caller's ephemeral SSH public key. declaredScopes carries the LOCAL
	// manifest's `scopes` so the server can grant them to an UNSUBMITTED app's
	// tunnel token (empty = read-only). Returns the assigned host + the /apps/dev
	// URL the developer opens.
	StartDevTunnel(ctx context.Context, blockID, sshPublicKey string, declaredScopes []string) (*DevTunnelSession, error)
	// StopDevTunnel revokes the caller's tunnel by sessionId (preferred) or, when
	// sessionId is empty, by blockId. Returns whether a session was torn down.
	StopDevTunnel(ctx context.Context, sessionID, blockID string) (bool, error)
}

DevTunnelController mints + revokes a dev-tunnel session. Behind an interface so the command layer is testable without a live server.

type DevTunnelForbiddenError added in v0.1.45

type DevTunnelForbiddenError struct {
	ServerMsg         string
	InsufficientScope bool
}

DevTunnelForbiddenError is returned when the dev-tunnel mint is refused with 403. Typed so the command layer can errors.As it and give the RIGHT fix, which differs by cause:

  • InsufficientScope: the CLI's credential lacks Full scope (the token-scope gate runs before the author/flag gates) → fix is a full-scope personal API key, NOT a different account.
  • otherwise: the account lacks the Apps-author invite + dev-tunnel flag → fix is signing in as an enrolled account.

func (*DevTunnelForbiddenError) Error added in v0.1.45

func (e *DevTunnelForbiddenError) Error() string

type DevTunnelSession added in v0.1.41

type DevTunnelSession struct {
	SessionID string `json:"sessionId"`
	// Host is the assigned unguessable `dev-<16hex>.<APPS_DOMAIN>` the reverse
	// tunnel binds to; the CLI passes it to `ssh -R` as the remote bind host.
	Host string `json:"host"`
	// URL is the `/apps/dev/<blockId>` page the developer opens in their browser.
	URL string `json:"url"`
	// ExpiresAt is the hard-TTL expiry (unix seconds) after which the server
	// reaper reclaims the route even if the CLI never calls stopDevTunnel.
	ExpiresAt int64 `json:"expiresAt"`
	// SpendCapBuzz is the per-session cumulative Buzz ceiling (backstop).
	SpendCapBuzz int64 `json:"spendCapBuzz"`
	// SSHHostPublicKey is the sish endpoint's OpenSSH host public-key line
	// (`ssh-ed25519 AAAA...`) — a NON-SECRET value the CLI PINS as the SSH
	// HostKeyCallback so the `ssh -R` bind can't be MITM'd (an on-path attacker
	// impersonating sish would reach the dev's localhost + tamper tunneled
	// traffic). The mint returns it; the CLI fails closed if it is absent
	// (never falls back to InsecureIgnoreHostKey).
	SSHHostPublicKey string `json:"sshHostPublicKey"`
}

DevTunnelSession mirrors blocks.startDevTunnel's result (the server's StartDevTunnelResult in dev-tunnel.service.ts). Field names + JSON casing track the server EXACTLY.

type DeviceAuth

type DeviceAuth struct {
	DeviceCode              string `json:"device_code"`
	UserCode                string `json:"user_code"`
	VerificationURI         string `json:"verification_uri"`
	VerificationURIComplete string `json:"verification_uri_complete"`
	ExpiresIn               int    `json:"expires_in"`
	Interval                int    `json:"interval"`
}

DeviceAuth is the device-init response.

type DeviceFlowError

type DeviceFlowError struct {
	Code        string
	Description string
}

DeviceFlowError is a terminal OAuth error (expired_token, access_denied, …) surfaced to the caller. authorization_pending / slow_down are handled inline by PollToken and never returned as this.

func (*DeviceFlowError) Error

func (e *DeviceFlowError) Error() string

type Downloader added in v0.1.63

type Downloader interface {
	// DownloadFile issues an authenticated GET to fileURL and returns the open
	// HTTP response for streaming. The CALLER owns resp.Body and MUST close it.
	// Redirects are followed automatically; the Civitai `/api/download` route
	// 302s to signed object storage on a DIFFERENT host, and Go's default client
	// strips the Authorization header on a cross-host redirect (exactly what we
	// want — the signed URL needs no bearer). A 401 with a refreshable token
	// source is retried once after a transparent refresh.
	DownloadFile(ctx context.Context, fileURL string) (*http.Response, error)
}

Downloader streams a file's bytes from a (possibly redirecting) URL. Behind an interface so the command layer stays testable without a live server.

type FileHashes added in v0.1.63

type FileHashes struct {
	AutoV1 string `json:"AutoV1,omitempty"`
	AutoV2 string `json:"AutoV2,omitempty"`
	SHA256 string `json:"SHA256,omitempty"`
	CRC32  string `json:"CRC32,omitempty"`
	BLAKE3 string `json:"BLAKE3,omitempty"`
}

FileHashes is the per-file hash set the API returns under `files[].hashes`. The keys are UPPER-cased algorithm names on the wire (AutoV1, AutoV2, SHA256, CRC32, BLAKE3); SHA256 is the one `download` verifies against.

type ForgejoCloneInfo added in v0.1.38

type ForgejoCloneInfo struct {
	NotYetAvailable bool   `json:"notYetAvailable"`
	Slug            string `json:"slug"`
	Message         string `json:"message"`
	ForgejoUsername string `json:"forgejoUsername"`
	Token           string `json:"token"`
	HTTPURL         string `json:"httpUrl"`
	CloneURL        string `json:"cloneUrl"`
}

ForgejoCloneInfo mirrors the getMyForgejoCloneInfo result. When the app's first version has not yet been ZIP-approved the server returns NotYetAvailable=true (no credential is minted) with a Message explaining why.

type Identity

type Identity struct {
	Username string `json:"username"`
	ID       int    `json:"id"`
	// TokenScope is the bearer token's scope bitmask. Decode it with the Scope*
	// bits below to learn what the credential can do (spend Buzz, read balance,
	// …). A personal full-scope key has every bit; an OAuth device-login token
	// typically has neither AIServicesWrite nor BuzzRead. nil ⇒ unknown (absent
	// from the response, e.g. cookie auth).
	TokenScope *int `json:"tokenScope,omitempty"`
	// BuzzLimit is the credential's raw per-window spend-cap payload as returned
	// by the server. Its shape is server-owned and has changed over time (a bare
	// number in older responses, an array of {type,limit,window,unit} windows in
	// current ones), so it is kept as RawMessage: whoami does not render it, and
	// it must never break the parse of the core identity. nil ⇒ absent/unknown.
	BuzzLimit json.RawMessage `json:"buzzLimit,omitempty"`
	// Subject identifies the credential (OAuth login vs personal API key). nil ⇒
	// cookie/session auth (not applicable to the CLI).
	Subject *Subject `json:"subject,omitempty"`
}

Identity is the authenticated-user view `whoami` reports. TokenScope and Subject are pointers because GET /api/v1/me omits them for some auth kinds (e.g. cookie/session), and a nil TokenScope must degrade to "scopes unknown" rather than decode as "no capabilities". The volatile, unrendered fields (BuzzLimit, Subject.ID) are json.RawMessage so a server-side type change to a peripheral field can never break the parse of the core identity whoami prints (see WhoAmI's core-identity fallback for the belt-and-suspenders guarantee).

func (*Identity) CanReadBuzz added in v0.1.9

func (id *Identity) CanReadBuzz() bool

CanReadBuzz reports whether the identity's token can read the Buzz balance. An unknown scope is treated as false.

func (*Identity) CanSpendBuzz added in v0.1.9

func (id *Identity) CanSpendBuzz() bool

CanSpendBuzz reports whether the identity's token carries the AI-Services (Buzz-spend) scope. An unknown scope is treated as false.

func (*Identity) CredentialType added in v0.1.38

func (id *Identity) CredentialType() string

CredentialType is a human label for the credential behind the token: "OAuth login", "personal API key", or "unknown" when the subject is absent.

func (*Identity) DecodeScopes added in v0.1.38

func (id *Identity) DecodeScopes() []string

DecodeScopes returns the names of every set scope bit (low → high). A nil (unknown) mask returns nil.

func (*Identity) IsOAuth added in v0.1.38

func (id *Identity) IsOAuth() bool

IsOAuth reports whether the credential is an OAuth device-login token (subject.type == "oauth"). A nil/absent subject is not OAuth.

func (*Identity) ScopeKnown added in v0.1.38

func (id *Identity) ScopeKnown() bool

ScopeKnown reports whether the identity carries a decodable scope bitmask. When false, capability queries are unknowable and the caller should say so rather than reporting "no".

type ImageItem added in v0.1.62

type ImageItem struct {
	ID        int             `json:"id"`
	URL       string          `json:"url"`
	Width     int             `json:"width"`
	Height    int             `json:"height"`
	NSFWLevel string          `json:"nsfwLevel"`
	Type      string          `json:"type"`
	PostID    *int            `json:"postId"`
	Username  string          `json:"username"`
	BaseModel string          `json:"baseModel"`
	Stats     ImageStats      `json:"stats"`
	Meta      json.RawMessage `json:"meta"`
}

ImageItem is the subset of a `GET /api/v1/images` item the CLI renders. The full item (tags, hash, and any meta fields beyond those in ImageMeta) is preserved for --json via Raw.

Meta is kept as json.RawMessage — not a decoded *ImageMeta — so a malformed meta on any single image can never fail the whole-page decode (which would also break `--meta --json`, an advertised raw passthrough). It is decoded best-effort at render time via ParseMeta.

func (ImageItem) ParseMeta added in v0.1.74

func (im ImageItem) ParseMeta() (ImageMeta, MetaState)

ParseMeta best-effort decodes the raw meta. It never returns an error: an absent or null meta → MetaAbsent, a present-but-unexpected shape → MetaUnparseable, so one odd image can't fail the page or the --json passthrough.

type ImageMeta added in v0.1.74

type ImageMeta struct {
	Prompt         string          `json:"prompt"`
	NegativePrompt string          `json:"negativePrompt"`
	Sampler        string          `json:"sampler"`
	CfgScale       json.RawMessage `json:"cfgScale"`
	Steps          json.RawMessage `json:"steps"`
	Seed           json.RawMessage `json:"seed"`
	Model          string          `json:"Model"` // capitalized in the API payload
}

ImageMeta is the generation metadata attached to an image when it is requested with `withMeta=true` (and `flatMeta=true`, which forces the flat shape on every query route so this can be parsed uniformly).

The numeric-ish fields (cfgScale/steps/seed) are json.RawMessage rather than a concrete number: the values are generator-supplied and freeform — a seed can exceed int32 (e.g. 557589798350441), cfgScale can be a float, and a field can occasionally arrive as an empty string, a non-numeric string, a bool, or an array. Capturing the raw bytes means one surprising field can never fail the decode; render them with CfgScaleString/StepsString/SeedString.

func (ImageMeta) CfgScaleString added in v0.1.74

func (m ImageMeta) CfgScaleString() string

CfgScaleString renders the cfgScale for display (empty when absent).

func (ImageMeta) SeedString added in v0.1.74

func (m ImageMeta) SeedString() string

SeedString renders the seed for display (empty when absent).

func (ImageMeta) StepsString added in v0.1.74

func (m ImageMeta) StepsString() string

StepsString renders the steps for display (empty when absent).

type ImageSearchResult added in v0.1.62

type ImageSearchResult struct {
	Items    []ImageItem `json:"items"`
	Metadata Metadata    `json:"metadata"`
	Raw      []byte      `json:"-"`
}

ImageSearchResult bundles the parsed items + pagination metadata with the raw response body (for --json passthrough).

type ImageStats added in v0.1.62

type ImageStats struct {
	LikeCount    int `json:"likeCount"`
	HeartCount   int `json:"heartCount"`
	LaughCount   int `json:"laughCount"`
	CryCount     int `json:"cryCount"`
	CommentCount int `json:"commentCount"`
}

ImageStats is the reaction/comment stats block on an image item.

type MetaState added in v0.1.74

type MetaState int

MetaState classifies an image's meta field for rendering.

const (
	// MetaAbsent: meta was not requested, or the API returned meta:null (the
	// uploader hid their generation data).
	MetaAbsent MetaState = iota
	// MetaOK: meta parsed into an ImageMeta object.
	MetaOK
	// MetaUnparseable: meta was present but not the expected object shape (e.g.
	// an array, or a scalar) — degrade gracefully rather than fail the page.
	MetaUnparseable
)

type Metadata added in v0.1.62

type Metadata struct {
	NextCursor  json.RawMessage `json:"nextCursor,omitempty"`
	NextPage    string          `json:"nextPage,omitempty"`
	PrevPage    string          `json:"prevPage,omitempty"`
	CurrentPage *int            `json:"currentPage,omitempty"`
	PageSize    *int            `json:"pageSize,omitempty"`
	TotalItems  *int            `json:"totalItems,omitempty"`
	TotalPages  *int            `json:"totalPages,omitempty"`
}

Metadata is the pagination envelope the list endpoints return under `metadata`. Not every endpoint sets every field: the cursor-paged feeds (models/images) set nextCursor/nextPage (+ currentPage/pageSize when page-paged), while the tRPC-backed lists (tags/creators) set the classic totalItems/currentPage/pageSize/totalPages + nextPage/prevPage.

nextCursor is left as RawMessage because the server emits it as a string, a number, or a date depending on the endpoint's sort key; CursorString renders it as a plain string for the user to feed back via --cursor.

func (Metadata) CursorString added in v0.1.62

func (m Metadata) CursorString() string

CursorString renders nextCursor as a bare string (unquoting a JSON string value) for display + re-use via --cursor. Returns "" when there is no cursor.

type ModelDetail added in v0.1.62

type ModelDetail struct {
	ID            int                   `json:"id"`
	Name          string                `json:"name"`
	Type          string                `json:"type"`
	NSFW          bool                  `json:"nsfw"`
	Creator       *Creator              `json:"creator"`
	Tags          []string              `json:"tags"`
	Stats         ModelStats            `json:"stats"`
	ModelVersions []ModelVersionSummary `json:"modelVersions"`
}

ModelDetail is the subset of `GET /api/v1/models/{id}` the CLI renders.

type ModelListItem added in v0.1.62

type ModelListItem struct {
	ID      int        `json:"id"`
	Name    string     `json:"name"`
	Type    string     `json:"type"`
	NSFW    bool       `json:"nsfw"`
	Creator *Creator   `json:"creator"`
	Stats   ModelStats `json:"stats"`
}

ModelListItem is the subset of a `GET /api/v1/models` item the CLI renders in its compact list. The full item (versions, files, images, license flags) is preserved for --json via the result's Raw bytes.

type ModelSearchResult added in v0.1.62

type ModelSearchResult struct {
	Items    []ModelListItem `json:"items"`
	Metadata Metadata        `json:"metadata"`
	Raw      []byte          `json:"-"`
}

ModelSearchResult bundles the parsed items + pagination metadata with the raw response body (for --json passthrough).

type ModelStats added in v0.1.62

type ModelStats struct {
	DownloadCount int `json:"downloadCount"`
	ThumbsUpCount int `json:"thumbsUpCount"`
	CommentCount  int `json:"commentCount"`
}

ModelStats is the AllTime stats block the model list/detail endpoints embed.

type ModelVersionDetail added in v0.1.62

type ModelVersionDetail struct {
	ID           int                `json:"id"`
	ModelID      int                `json:"modelId"`
	Name         string             `json:"name"`
	BaseModel    string             `json:"baseModel"`
	AIR          string             `json:"air"`
	DownloadURL  string             `json:"downloadUrl"`
	TrainedWords []string           `json:"trainedWords"`
	Model        *ModelVersionModel `json:"model"`
	Files        []ModelVersionFile `json:"files"`
	Stats        ModelVersionStats  `json:"stats"`
}

ModelVersionDetail is the subset of `GET /api/v1/model-versions/{id}` (and `/by-hash/{hash}`) the CLI renders. The full body (images, all files with hashes) is preserved for --json via the raw bytes the getter returns.

type ModelVersionFile added in v0.1.62

type ModelVersionFile struct {
	ID          int        `json:"id"`
	Name        string     `json:"name"`
	Type        string     `json:"type"`
	SizeKB      float64    `json:"sizeKB"`
	Primary     bool       `json:"primary"`
	DownloadURL string     `json:"downloadUrl"`
	Hashes      FileHashes `json:"hashes"`
}

ModelVersionFile is the subset of a model-version `files[]` entry the CLI lists and downloads. ID/Primary/DownloadURL/Hashes are carried (beyond the display-only Name/Type/SizeKB) because `download` selects + fetches + verifies individual files. Field names track the live API EXACTLY (verified against GET /api/v1/model-versions/{id}: id, name, type, sizeKB, primary, downloadUrl, hashes{SHA256,…}).

func PrimaryFile added in v0.1.63

func PrimaryFile(files []ModelVersionFile) *ModelVersionFile

PrimaryFile returns the primary file of a version's file list (the one flagged `primary: true`), falling back to the first file when none is flagged. Returns nil for an empty list. This is the file `download` fetches by default and the one the list/detail renderers inspect for the non-weights marker.

func (*ModelVersionFile) IsModelWeights added in v0.1.63

func (f *ModelVersionFile) IsModelWeights() bool

IsModelWeights reports whether the file is the main model-weights file (type == "Model"), as opposed to an archive / training data / config / a component file. Used only for the informational non-weights marker.

type ModelVersionModel added in v0.1.62

type ModelVersionModel struct {
	Name string `json:"name"`
	Type string `json:"type"`
	NSFW bool   `json:"nsfw"`
}

ModelVersionModel is the embedded parent-model summary on a version detail.

type ModelVersionStats added in v0.1.62

type ModelVersionStats struct {
	DownloadCount int `json:"downloadCount"`
	ThumbsUpCount int `json:"thumbsUpCount"`
}

ModelVersionStats is the version-level stats block.

type ModelVersionSummary added in v0.1.62

type ModelVersionSummary struct {
	ID        int                `json:"id"`
	Name      string             `json:"name"`
	BaseModel string             `json:"baseModel"`
	Files     []ModelVersionFile `json:"files"`
}

ModelVersionSummary is the per-version summary shown under a model detail. Files is carried so the detail renderer can tag a version whose primary file is not model weights with its file type (an informational marker). The full GET /api/v1/models/{id} response embeds each version's files[].

type OAuthClient

type OAuthClient struct {
	BaseURL string
	HTTP    *http.Client
	// contains filtered or unexported fields
}

OAuthClient talks the device-flow + refresh endpoints.

func NewOAuthClient

func NewOAuthClient(baseURL string) *OAuthClient

NewOAuthClient builds an OAuthClient with sane defaults.

func (*OAuthClient) PollToken

func (c *OAuthClient) PollToken(ctx context.Context, auth *DeviceAuth, sleep func(time.Duration)) (*TokenResponse, error)

PollToken polls device-token until approval, a terminal error, or the device-flow deadline (auth.ExpiresIn). It blocks for `interval` seconds between polls and increases the interval by 5s on slow_down. sleep is injectable for tests; pass nil for the real time.Sleep.

func (*OAuthClient) Refresh

func (c *OAuthClient) Refresh(ctx context.Context, refreshToken string) (*TokenResponse, error)

Refresh exchanges a refresh token for a new access token. The server may rotate the refresh token; callers must persist tr.RefreshToken if non-empty.

func (*OAuthClient) StartDevice

func (c *OAuthClient) StartDevice(ctx context.Context) (*DeviceAuth, error)

StartDevice initiates the device-authorization grant.

The device endpoint (auth.civitai.com) requires application/x-www-form-urlencoded AND a same-origin Origin header — a JSON body 400s ("Missing client_id") and a form POST without a matching Origin 403s ("Cross-site POST form submissions are forbidden"). The endpoint URL + Origin come from OpenID discovery.

type Reader added in v0.1.62

type Reader interface {
	SearchModels(ctx context.Context, q url.Values) (*ModelSearchResult, error)
	GetModel(ctx context.Context, id string) (*ModelDetail, []byte, error)
	GetModelVersion(ctx context.Context, id string) (*ModelVersionDetail, []byte, error)
	GetModelVersionByHash(ctx context.Context, hash string) (*ModelVersionDetail, []byte, error)
	SearchImages(ctx context.Context, q url.Values) (*ImageSearchResult, error)
	SearchTags(ctx context.Context, q url.Values) (*TagSearchResult, error)
	SearchCreators(ctx context.Context, q url.Values) (*CreatorSearchResult, error)
	SearchUsers(ctx context.Context, q url.Values) (*UserSearchResult, error)
	SearchArticles(ctx context.Context, q url.Values) (*ArticleSearchResult, error)
	GetArticle(ctx context.Context, id string) (*ArticleDetail, []byte, error)
	SearchCollections(ctx context.Context, q url.Values) (*CollectionSearchResult, error)
	GetCollection(ctx context.Context, id string) (*CollectionDetail, []byte, error)
}

Reader is the read surface of the public Civitai REST API (`/api/v1/**`).

These are all public GET endpoints: they accept the CLI's bearer token when one is configured (a personal API key or an OAuth device-login access token, refreshed transparently on a 401) but also work fully anonymously, since the public read routes do not enforce scope. Build the Client with an empty token (api.New(base, "", "")) to force anonymous requests.

type Scope

type Scope string

Scope is a token scope that tolerates BOTH JSON shapes the server emits: the device-token (login) route returns a plain string (`scope.toString()`), while the token/refresh route returns the @node-oauth/oauth2-server shape where scope is an ARRAY of strings (e.g. ["33554433"]). Declaring scope as a plain `string` made json.Unmarshal of the refresh response fail the whole struct, killing Refresh() after the 1h access-token TTL. UnmarshalJSON normalizes either shape to a single space-joined string (OAuth convention). The CLI only stores/displays scope, it never enforces it, so this is safe.

func (Scope) String

func (s Scope) String() string

String returns the scope as a plain string for storage/display.

func (*Scope) UnmarshalJSON

func (s *Scope) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts a JSON string OR a JSON array of strings.

type StaticToken

type StaticToken string

StaticToken is a TokenSource for a fixed token (personal API key). It never refreshes.

func (StaticToken) Refresh

func (s StaticToken) Refresh(context.Context) (string, error)

Refresh always fails: a personal key has no refresh path.

func (StaticToken) Token

func (s StaticToken) Token(context.Context) (string, error)

Token returns the static token.

type StatusReader added in v0.1.2

type StatusReader interface {
	// ListSubmissions returns the caller's submissions, newest first. An empty
	// blockId lists all of them.
	ListSubmissions(ctx context.Context, blockID string) ([]Submission, error)
	// GetSubmission returns a single submission. Exactly one of id (a
	// pubreq_<ULID>) or blockID (an app slug) must be set.
	GetSubmission(ctx context.Context, id, blockID string) (*Submission, error)
}

StatusReader reads the caller's own App-Block submission review/deploy state.

type Subject added in v0.1.38

type Subject struct {
	Type string `json:"type"`
	// ID is the credential's identifier. Its JSON shape is server-owned and
	// varies by credential kind — a numeric api-key id (e.g. 96633526) or a
	// string oauth subject — so it is kept as RawMessage to tolerate either
	// shape. whoami does not render it; only Type drives CredentialType/IsOAuth.
	ID json.RawMessage `json:"id,omitempty"`
}

Subject identifies the credential behind a token as returned by GET /api/v1/me. Type == "oauth" means an OAuth device-login token (from `civitai login`); any other type (e.g. "apiKey"/"user") is a personal API key. Absent when auth is cookie/session (not applicable to the CLI).

type Submission added in v0.1.2

type Submission struct {
	ID              string  `json:"id"`
	BlockID         string  `json:"blockId"` // the app slug; builds <blockId>.civit.ai
	AppBlockID      *string `json:"appBlockId"`
	Version         string  `json:"version"`
	Status          string  `json:"status"` // pending | approved | rejected | withdrawn
	RejectionReason *string `json:"rejectionReason"`
	ApprovalNotes   *string `json:"approvalNotes"`
	DeployState     *string `json:"deployState"` // null | building | deploying | live | failed
	DeployDetail    *string `json:"deployDetail"`
	DeployUpdatedAt *string `json:"deployUpdatedAt"`
	SubmittedAt     string  `json:"submittedAt"`
	ReviewedAt      *string `json:"reviewedAt"`
	UpdatedAt       string  `json:"updatedAt"`
	CreatedAt       string  `json:"createdAt"`
	LiveURL         *string `json:"liveUrl"` // set once serving (approved+live)
}

Submission mirrors the shaped row from GET /api/v1/blocks/submissions (civitai/civitai src/pages/api/v1/blocks/submissions.ts -> shapeRow). Field names + JSON casing track the server EXACTLY.

type SubmitResult

type SubmitResult struct {
	PublishRequestID string `json:"publishRequestId"`
	Slug             string `json:"slug"`
	Version          string `json:"version"`
	Status           string `json:"status"`
}

SubmitResult is the publish-request result the server returns.

type Submitter

type Submitter interface {
	SubmitVersion(ctx context.Context, zipBytes []byte, slug, version string) (*SubmitResult, error)
}

Submitter submits a packaged bundle and returns the server's response. The slug + version identify the submission so that, if the upload's response is lost to a timeout, the submit path can poll for a landed submission and recover rather than reporting a false failure (see SubmitVersion).

type TagItem added in v0.1.62

type TagItem struct {
	Name string `json:"name"`
	Link string `json:"link"`
}

TagItem is a `GET /api/v1/tags` item: the tag name + a convenience link to the models filtered by it.

type TagSearchResult added in v0.1.62

type TagSearchResult struct {
	Items    []TagItem `json:"items"`
	Metadata Metadata  `json:"metadata"`
	Raw      []byte    `json:"-"`
}

TagSearchResult bundles parsed tags + metadata with the raw body.

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
	Scope        Scope  `json:"scope"`
}

TokenResponse is the successful device-token / refresh response. RefreshToken may be empty on a refresh that doesn't rotate.

type TokenSource

type TokenSource interface {
	// Token returns the current bearer token, refreshing first if it is known
	// to be expired. An empty token means "unauthenticated".
	Token(ctx context.Context) (string, error)
	// Refresh forces a refresh (used on a 401) and returns the new token.
	// Sources that cannot refresh return ("", ErrNoRefresh).
	Refresh(ctx context.Context) (string, error)
}

TokenSource yields the Bearer token to send. Refresh, if supported, refreshes the token (e.g. after a 401) and returns the new one; a static personal-key source returns ErrNoRefresh.

type UserItem added in v0.1.62

type UserItem struct {
	ID       int    `json:"id"`
	Username string `json:"username"`
	Image    string `json:"image"`
}

UserItem is the subset of a `GET /api/v1/users` search item the CLI renders. The public users search returns basic identity fields; richer fields (status/avatar) are only included for internal system requests.

type UserSearchResult added in v0.1.62

type UserSearchResult struct {
	Items []UserItem `json:"items"`
	Raw   []byte     `json:"-"`
}

UserSearchResult bundles parsed users with the raw body. The public users endpoint returns only `{ items }` (no pagination metadata).

type Verifier

type Verifier interface {
	WhoAmI(ctx context.Context) (*Identity, error)
}

Verifier verifies a token and returns the authenticated identity.

type Withdrawer added in v0.1.8

type Withdrawer interface {
	// WithdrawRequest withdraws the publish request with the given id. It is
	// idempotent: a 200 (incl. already-withdrawn) is success; a 409 means the
	// request is not in a withdrawable (pending) state.
	WithdrawRequest(ctx context.Context, publishRequestID string) error
}

Withdrawer withdraws the caller's own pending App-Block publish request so a new bundle can be submitted for the same slug.

Jump to

Keyboard shortcuts

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