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
- Variables
- func DownloadNeedsAuth(fileURL, baseURL string) bool
- type ArticleDetail
- type ArticleDetailStats
- type ArticleListItem
- type ArticleSearchResult
- type ArticleStats
- type ArticleTag
- type ArticleUser
- type BuzzAccount
- type BuzzReader
- type Client
- func (c *Client) DownloadFile(ctx context.Context, fileURL string) (*http.Response, error)
- func (c *Client) GetArticle(ctx context.Context, id string) (*ArticleDetail, []byte, error)
- func (c *Client) GetBuzzAccount(ctx context.Context) (*BuzzAccount, error)
- func (c *Client) GetCollection(ctx context.Context, id string) (*CollectionDetail, []byte, error)
- func (c *Client) GetForgejoCloneInfo(ctx context.Context, app string) (*ForgejoCloneInfo, error)
- func (c *Client) GetModel(ctx context.Context, id string) (*ModelDetail, []byte, error)
- func (c *Client) GetModelVersion(ctx context.Context, id string) (*ModelVersionDetail, []byte, error)
- func (c *Client) GetModelVersionByHash(ctx context.Context, hash string) (*ModelVersionDetail, []byte, error)
- func (c *Client) GetSubmission(ctx context.Context, id, blockID string) (*Submission, error)
- func (c *Client) ListSubmissions(ctx context.Context, blockID string) ([]Submission, error)
- func (c *Client) MintDevToken(ctx context.Context, slug string, scopes []string) (string, error)
- func (c *Client) SearchArticles(ctx context.Context, q url.Values) (*ArticleSearchResult, error)
- func (c *Client) SearchCollections(ctx context.Context, q url.Values) (*CollectionSearchResult, error)
- func (c *Client) SearchCreators(ctx context.Context, q url.Values) (*CreatorSearchResult, error)
- func (c *Client) SearchImages(ctx context.Context, q url.Values) (*ImageSearchResult, error)
- func (c *Client) SearchModels(ctx context.Context, q url.Values) (*ModelSearchResult, error)
- func (c *Client) SearchTags(ctx context.Context, q url.Values) (*TagSearchResult, error)
- func (c *Client) SearchUsers(ctx context.Context, q url.Values) (*UserSearchResult, error)
- func (c *Client) StartDevTunnel(ctx context.Context, blockID, sshPublicKey string, declaredScopes []string) (*DevTunnelSession, error)
- func (c *Client) StopDevTunnel(ctx context.Context, sessionID, blockID string) (bool, error)
- func (c *Client) SubmitVersion(ctx context.Context, zipBytes []byte, slug, version string) (*SubmitResult, error)
- func (c *Client) WhoAmI(ctx context.Context) (*Identity, error)
- func (c *Client) WithdrawRequest(ctx context.Context, publishRequestID string) error
- type CollectionDetail
- type CollectionListItem
- type CollectionSearchResult
- type CollectionTag
- type CollectionUser
- type Creator
- type CreatorItem
- type CreatorSearchResult
- type DevTokenMinter
- type DevTunnelController
- type DevTunnelForbiddenError
- type DevTunnelSession
- type DeviceAuth
- type DeviceFlowError
- type Downloader
- type FileHashes
- type ForgejoCloneInfo
- type Identity
- type ImageItem
- type ImageMeta
- type ImageSearchResult
- type ImageStats
- type MetaState
- type Metadata
- type ModelDetail
- type ModelListItem
- type ModelSearchResult
- type ModelStats
- type ModelVersionDetail
- type ModelVersionFile
- type ModelVersionModel
- type ModelVersionStats
- type ModelVersionSummary
- type OAuthClient
- type Reader
- type Scope
- type StaticToken
- type StatusReader
- type Subject
- type Submission
- type SubmitResult
- type Submitter
- type TagItem
- type TagSearchResult
- type TokenResponse
- type TokenSource
- type UserItem
- type UserSearchResult
- type Verifier
- type Withdrawer
Constants ¶
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.
const ( StartDevTunnelPath = "/api/trpc/blocks.startDevTunnel" StopDevTunnelPath = "/api/trpc/blocks.stopDevTunnel" )
StartDevTunnelPath / StopDevTunnelPath are the non-batched tRPC routes.
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.
const BuzzAccountPath = "/api/trpc/buzz.getBuzzAccount"
BuzzAccountPath is the tRPC route that returns the spendable Buzz balance.
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`).
const DefaultSubmitPath = "/api/v1/blocks/submit-version"
DefaultSubmitPath is the token-authenticated submit-version route.
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.
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.
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).
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 ¶
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.
var ErrNoRefresh = fmt.Errorf("token source cannot refresh")
ErrNoRefresh is returned by a non-refreshable (personal-key) TokenSource.
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
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
ArticleTag is a tag attached to an article (`tags[]` on both list + detail).
type ArticleUser ¶ added in v0.1.62
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 ¶
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
DownloadFile implements Downloader. See the interface doc for the contract.
func (*Client) GetArticle ¶ added in v0.1.62
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
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
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
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
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
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
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
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
SearchCreators calls GET /api/v1/creators.
func (*Client) SearchImages ¶ added in v0.1.62
SearchImages calls GET /api/v1/images with the given query params.
func (*Client) SearchModels ¶ added in v0.1.62
SearchModels calls GET /api/v1/models with the given query params.
func (*Client) SearchTags ¶ added in v0.1.62
SearchTags calls GET /api/v1/tags.
func (*Client) SearchUsers ¶ added in v0.1.62
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
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 ¶
WhoAmI verifies the token against /api/v1/me, refreshing the OAuth access token transparently if needed.
func (*Client) WithdrawRequest ¶ added in v0.1.8
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
CollectionTag is a tag attached to a collection (detail-only `tags[]`).
type CollectionUser ¶ added in v0.1.62
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
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 ¶
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
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
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
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
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
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
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.
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
CfgScaleString renders the cfgScale for display (empty when absent).
func (ImageMeta) SeedString ¶ added in v0.1.74
SeedString renders the seed for display (empty when absent).
func (ImageMeta) StepsString ¶ added in v0.1.74
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
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) UnmarshalJSON ¶
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.
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
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
UserSearchResult bundles parsed users with the raw body. The public users endpoint returns only `{ items }` (no pagination metadata).
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.