Documentation
¶
Overview ¶
Package api is the (thin) HTTP client for the Civitai App Blocks 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
- type Client
- 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) 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 DeviceAuth
- type DeviceFlowError
- type Identity
- type OAuthClient
- type Scope
- type StaticToken
- type StatusReader
- type Submission
- type SubmitResult
- type Submitter
- type TokenResponse
- type TokenSource
- type Verifier
- type Withdrawer
Constants ¶
const ( // ClientID is the public OAuth client id for the CLI. ClientID = "civitai-cli" // DeviceScope is UserRead|AppBlocksSubmit (bit flags), the fixed scope the CLI // requests on login. 33554433 == (1<<0)|(1<<25) in the server's scope bitset: // - 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). // This is exactly the civitai-cli OauthClient.allowedScopes set (33554433). 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. // // What a login token can do: it can MINT an App-Blocks dev token (the mint gate // only needs AppBlocksSubmit) and drive the read/estimate harness paths — cost // preview / whatif, catalog browsing, and 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 = "33554433" )
OAuth device-authorization-grant client for the civitai-cli public client.
Contract (civitai/civitai PR #2644):
- device init: POST {BaseURL}/api/auth/oauth/device
- device poll: POST {BaseURL}/api/auth/oauth/device-token
- token refresh: POST {BaseURL}/api/auth/oauth/token
civitai-cli is a PUBLIC client (PKCE/device): no client secret.
const DefaultSubmitPath = "/api/v1/blocks/submit-version"
DefaultSubmitPath is the token-authenticated submit-version route.
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 ErrNoRefresh = fmt.Errorf("token source cannot refresh")
ErrNoRefresh is returned by a non-refreshable (personal-key) TokenSource.
Functions ¶
This section is empty.
Types ¶
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
}
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) 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) 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 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 OAuthClient ¶
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.
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 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 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 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.