Documentation
¶
Overview ¶
Package popnow is the Go client for the PopNow API.
PopNow is a browser-based AI studio for videos, images and music. Its developer API serves the same image, video and audio models, prices and credit wallet as https://popnow.ai. This package covers the asynchronous media workflow end to end: discover a model and its input schema, upload local reference material, quote a request, submit it, wait for a terminal state, collect the result, keep it in the asset library, and verify the webhook that announces it.
It depends on the standard library only.
Two things decide most of the code in this package:
- Every /api/v1 response is the {code,msg,data,request_id} envelope, and a successful HTTP status does not mean the call succeeded. Branch on the business code, never on the message text.
- HTTP 202 from task creation means accepted, not finished. Success or failure is reported by the task's state, read back with GetTask.
Billing: 1 credit = $0.01. Amounts are reported both as integer credits (MaxCredits, EstimatedCredits, Credits, ...) and as the equivalent USD decimal string (MaxCharge, EstimatedCost, Cost, ...).
The contract this package is written against is published at https://popnow.ai/docs/openapi.yaml.
Index ¶
- Constants
- Variables
- func NewIdempotencyKey() (string, error)
- type APIError
- type Asset
- type AssetDeleted
- type AssetLayer
- type AssetList
- type AssetPlan
- type AssetQuota
- type AssetScope
- type Balance
- type Character
- type CharacterList
- type Client
- func (c *Client) Assets(ctx context.Context, opts ListAssetsOptions) iter.Seq2[Asset, error]
- func (c *Client) CommitUpload(ctx context.Context, fileID string) (*UploadedFile, error)
- func (c *Client) CreateAsset(ctx context.Context, input CreateAssetInput, idempotencyKey string) (*Asset, error)
- func (c *Client) CreateDownloadURL(ctx context.Context, taskID, key string) (*DownloadTicket, error)
- func (c *Client) CreateTask(ctx context.Context, input CreateTaskInput, idempotencyKey string, ...) (*CreateTaskResult, error)
- func (c *Client) CreateUploadURL(ctx context.Context, contentType string, size int64) (*UploadTicket, error)
- func (c *Client) DeleteAsset(ctx context.Context, assetID string) (*AssetDeleted, error)
- func (c *Client) EachAsset(ctx context.Context, opts ListAssetsOptions, fn func(Asset) bool) error
- func (c *Client) EachTask(ctx context.Context, opts ListTasksOptions, fn func(TaskListItem) bool) error
- func (c *Client) GetAsset(ctx context.Context, assetID string) (*Asset, error)
- func (c *Client) GetAssetUsage(ctx context.Context) (*AssetQuota, error)
- func (c *Client) GetBalance(ctx context.Context) (*Balance, error)
- func (c *Client) GetCharacter(ctx context.Context, characterID string) (*Character, error)
- func (c *Client) GetModel(ctx context.Context, model string) (*Model, error)
- func (c *Client) GetTask(ctx context.Context, taskID string) (*Task, error)
- func (c *Client) GetUsage(ctx context.Context, opts UsageOptions) (*UsageReport, error)
- func (c *Client) ImportLoRA(ctx context.Context, input LoRAImport) (*UploadedFile, error)
- func (c *Client) ListAssets(ctx context.Context, opts ListAssetsOptions) (*AssetList, error)
- func (c *Client) ListCharacters(ctx context.Context, opts ListCharactersOptions) (*CharacterList, error)
- func (c *Client) ListModels(ctx context.Context, opts ListModelsOptions) (*ModelList, error)
- func (c *Client) ListTasks(ctx context.Context, opts ListTasksOptions) (*TaskList, error)
- func (c *Client) PurgeTask(ctx context.Context, taskID string) (*PurgeResult, error)
- func (c *Client) Quote(ctx context.Context, input CreateTaskInput) (*Quote, error)
- func (c *Client) RetryTask(ctx context.Context, taskID, idempotencyKey string) (*RetryTaskResult, error)
- func (c *Client) Tasks(ctx context.Context, opts ListTasksOptions) iter.Seq2[TaskListItem, error]
- func (c *Client) UpdateAsset(ctx context.Context, assetID string, input UpdateAssetInput) (*Asset, error)
- func (c *Client) Upload(ctx context.Context, data []byte, contentType string) (*UploadedFile, error)
- func (c *Client) UploadFile(ctx context.Context, path, contentType string) (*UploadedFile, error)
- func (c *Client) UploadReader(ctx context.Context, r io.Reader, size int64, contentType string) (*UploadedFile, error)
- func (c *Client) WaitForTerminal(ctx context.Context, taskID string, opts WaitOptions) (*Task, error)
- type CreateAssetInput
- type CreateTaskInput
- type CreateTaskOptions
- type CreateTaskResult
- type CreditFacility
- type DownloadTicket
- type EndpointEvent
- type FundingGrant
- type FundingOverview
- type HTTPDoer
- type ListAssetsOptions
- type ListCharactersOptions
- type ListModelsOptions
- type ListTasksOptions
- type LoRAImport
- type Model
- type ModelCreditRule
- type ModelCredits
- type ModelExample
- type ModelList
- type Options
- type OutputAsset
- type PurgeResult
- type Quote
- type RetryTaskResult
- type Task
- type TaskList
- type TaskListItem
- type TaskOutput
- type TaskRetention
- type TaskTranscript
- type TaskTranscriptChannel
- type TaskTranscriptWord
- type UpdateAssetInput
- type UploadTicket
- type UploadedFile
- type UsageDay
- type UsageModel
- type UsageOptions
- type UsageReport
- type WaitOptions
- type WaitTimeoutError
- type WebhookDelivery
Constants ¶
const ( AssetSourceTaskOutput = "task_output" AssetSourceUpload = "upload" AssetSourceURL = "url" )
Asset sources accepted by CreateAsset and reported on Asset.Source.
const ( // APIBaseURL is the production API root for the native endpoints. APIBaseURL = "https://api.popnow.ai/api/v1" // APIKeyEnv is the environment variable New reads when Options.APIKey is // empty. APIKeyEnv = "POPNOW_API_KEY" // DefaultRequestTimeout bounds one JSON API attempt, not the whole call. // Give the context its own deadline to bound a call including retries. DefaultRequestTimeout = 30 * time.Second // DefaultUploadTimeout bounds the PUT that streams bytes into object // storage, and the server-side download behind ImportLoRA. It is far // longer than DefaultRequestTimeout: audio and video may be 90 MiB and // LoRA weights 2 GiB, and a 30 second cut-off would read like a network // fault while really being this client hanging up on itself. DefaultUploadTimeout = 30 * time.Minute // DefaultWaitTimeout bounds WaitForTerminal. It is a local safety bound, // not a service level: reaching it says nothing about the remote task. DefaultWaitTimeout = 10 * time.Minute // DefaultPollInterval is the first gap between task polls; the gap grows // by half on every pass and stops at MaxPollInterval. DefaultPollInterval = 2 * time.Second // MaxPollInterval caps the growth of the polling gap. MaxPollInterval = 15 * time.Second // DefaultMaxRetries is how many extra attempts a replayable request gets. DefaultMaxRetries = 3 // WebhookTolerance is the default clock skew the webhook verifiers accept. WebhookTolerance = 5 * time.Minute )
Defaults for a new Client. Every one of them can be overridden in Options.
const ( StateQueued = "queued" StateRunning = "running" StateSucceeded = "succeeded" StateFailed = "failed" StateCanceled = "canceled" StateExpired = "expired" )
Task states. The two in-flight states are the stable part of this set: treat anything else as terminal rather than listing the terminal values, so that a state added later ends a wait instead of extending it forever. StateCanceled exists only on old records.
const ( ErrorCodeInvalidRequest = "invalid_request" ErrorCodeUnsupportedCombination = "unsupported_combination" ErrorCodeContentRejected = "content_rejected" ErrorCodeRateLimited = "rate_limited" ErrorCodeGenerationFailed = "generation_failed" ErrorCodeTimeout = "timeout" ErrorCodeInvalidAsset = "invalid_asset" ErrorCodeUpstreamFailed = "upstream_failed" )
Failure identifiers reported in Task.ErrorCode. The set is closed; handle any value you do not recognise the way you handle ErrorCodeUpstreamFailed.
const ( CodeSuccess = 200 CodeBadRequest = 400 CodeUploadMismatch = 40003 CodeUnsupportedCombination = 40004 CodeInsufficientCredits = 40201 CodeSpendCapReached = 40202 CodeForbidden = 403 CodeModelNotAllowed = 40301 CodeIPNotAllowed = 40302 CodeEmailNotVerified = 40310 CodeNotFound = 404 CodeConflict = 409 CodeQuoteRejected = 40901 CodePayloadTooLarge = 413 CodeRateLimited = 429 CodeServerError = 500 CodeGenerationRefunded = 50302 )
Business codes. They travel in the envelope's code field and are the thing to branch on: several of them share one HTTP status.
40003 the uploaded bytes do not match their ticket (size, media type or
signature). Not retryable: request a fresh ticket with the real
content type and size and upload again.
40004 the request is valid but no deployment serves this exact
combination of settings right now. Not retryable: change the
parameter named in the message, or pick another model.
40201 not enough credits for this task's hold. Buy credits, then resend.
40202 a key spend cap was reached.
40301 this key may not call this model.
40302 the caller's address is outside this key's IP allow-list.
40303 the service is not offered in the caller's region.
40310 the account has not verified its email address yet. The same key
works as soon as the address is verified.
40901 the quote expired, or the request or price changed since it was
signed. Quote again, then resubmit.
503 a supporting dependency is briefly unavailable. Retryable; honour
Retry-After when the response carries it.
50301 the model has no usable deployment or effective price right now.
50302 a synchronous generation failed and the charge was refunded.
Resend under a new idempotency key: the old key replays the
recorded failure. This client therefore never resends it for you.
503, 50301 and 50302 share HTTP 503 and mean three different things, which is why this client reads the business code first and the status second.
const ContentTypeLoRA = "application/octet-stream"
ContentTypeLoRA is the media type LoRA weights (.safetensors) upload under.
const UserAgent = "popnow-go/" + Version + " (+https://github.com/popnow-ai/popnow-go)"
UserAgent identifies this client on every API request.
It is always sent explicitly. Without it net/http sends Go-http-client/1.1, which says nothing beyond "a Go program", and the API edge refuses some generic default client agents outright. A versioned agent also makes a bad release or a version-specific bug visible in request logs.
const Version = "0.1.0"
Version is this package's released version, and the only place that number is written down. UserAgent derives from it, so bumping it here is the whole release edit.
Keep it equal to the git tag it ships under, without the leading v. A tagged module exposes its version only to the programs that depend on it, never to itself, so nothing can read it back at run time.
Variables ¶
var ( ErrWebhookRejected = errors.New("popnow: webhook rejected") ErrWebhookStale = fmt.Errorf("%w: timestamp outside the tolerance window", ErrWebhookRejected) ErrWebhookSignature = fmt.Errorf("%w: signature mismatch", ErrWebhookRejected) )
Webhook rejection reasons. Every one of them wraps ErrWebhookRejected, so a handler that only wants to answer 401 can test for that alone.
Functions ¶
func NewIdempotencyKey ¶
NewIdempotencyKey mints a key with enough entropy that two submissions never collide. Store it next to your own record of the job before you send it: the point of the key is to be available again after the crash that lost the response. A key derived from your own data ("order-8814-render-1") works just as well and can be recomputed instead of stored.
Types ¶
type APIError ¶
type APIError struct {
// Status is the HTTP status. It is 0 when no response was parsed.
Status int
// Code is the business code from the envelope, 0 when the body was not an
// envelope. Branch on this, not on Message.
Code int
// Message is the server's explanation, in English unless the account or
// the Accept-Language header selects another language. Never parse it.
Message string
// Reason is the machine-readable data.reason some failures carry, for
// example "asset_library_locked" or "asset_daily_limit". Empty when the
// failure has none. New values may appear; treat unknown ones as empty.
Reason string
// RequestID correlates this call with the server's logs.
RequestID string
// RetryAfter is the server's own backoff request, 0 when absent.
RetryAfter time.Duration
}
APIError is a rejected call: an HTTP status, the business code to branch on, and the request ID to quote to support. Recover it with errors.As.
type Asset ¶
type Asset struct {
ID string `json:"id"`
// URI is the stable popnow://a/ast_... reference. It works in any media
// field of a model input, for keys of the same workspace, and is never a
// downloadable link. Persist this, not PreviewURL.
URI string `json:"uri"`
Name string `json:"name"`
Kind string `json:"kind"`
ContentType string `json:"contentType"`
Bytes int64 `json:"bytes"`
// Width, Height and DurationSeconds are measured by the server, 0 when
// they do not apply.
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
DurationSeconds float64 `json:"durationSeconds,omitempty"`
// Source is task_output, upload or url; SourceID is your task or upload
// id, or an opaque digest of the address for url.
Source string `json:"source"`
SourceID string `json:"sourceId"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
// PreviewURL is a private preview signed for at most 20 minutes, empty
// when signing fails. Do not store or share it.
PreviewURL string `json:"previewUrl"`
PreviewURLExpiresAt *time.Time `json:"previewUrlExpiresAt,omitempty"`
// Favorite belongs to the library, so everyone in a team sees the same.
Favorite bool `json:"favorite"`
// Model and ModelName name the model that generated the asset; empty for
// uploads and links.
Model string `json:"model"`
ModelName string `json:"modelName"`
}
Asset is one file in the long-term private asset library.
The library is decided by the key: a personal key reaches your personal library, a team workspace key reaches that team's shared library. Saved files are kept until they are deleted.
type AssetDeleted ¶
AssetDeleted confirms DeleteAsset.
type AssetLayer ¶
type AssetLayer struct {
ZIndex int `json:"zIndex"`
// Name is the model's own label for the layer, when it gives one.
Name string `json:"name,omitempty"`
// BoundingBox is [x1, y1, x2, y2] in base-image pixels. It is nil on the
// base image and on models that do not report positions.
BoundingBox []int `json:"boundingBox,omitempty"`
}
AssetLayer places one output of a layer-decomposition task.
The first asset is the base image (ZIndex 0) and every other asset is one transparent PNG layer. Stack them by ascending ZIndex to rebuild the picture, rather than relying on the order of Assets.
type AssetList ¶
type AssetList struct {
Items []Asset `json:"items"`
// NextCursor is absent on the last page.
NextCursor string `json:"nextCursor,omitempty"`
}
AssetList is one page of the asset library.
type AssetPlan ¶
type AssetPlan struct {
ID string `json:"id"`
Name string `json:"name"`
MinTopupUsd string `json:"minTopupUsd"`
MaxBytes int64 `json:"maxBytes"`
MaxCount int64 `json:"maxCount"`
MaxFileBytes int64 `json:"maxFileBytes"`
}
AssetPlan is one storage plan of the asset library. Byte values use binary units.
type AssetQuota ¶
type AssetQuota struct {
Tier AssetPlan `json:"tier"`
Usage struct {
Count int64 `json:"count"`
Bytes int64 `json:"bytes"`
} `json:"usage"`
Limits struct {
Tier string `json:"tier"`
MaxCount int64 `json:"maxCount"`
MaxBytes int64 `json:"maxBytes"`
MaxFileBytes int64 `json:"maxFileBytes"`
GlobalMaxBytes int64 `json:"globalMaxBytes"`
NameMaxChars int `json:"nameMaxChars"`
} `json:"limits"`
// Full is true when no new asset fits.
Full bool `json:"full"`
// NextTier is nil on the highest plan.
NextTier *AssetPlan `json:"nextTier"`
// TopupToNextTierUsd and LifetimeTopupUsd are empty when Scope.CanTopUp
// is false.
TopupToNextTierUsd string `json:"topupToNextTierUsd,omitempty"`
LifetimeTopupUsd string `json:"lifetimeTopupUsd,omitempty"`
Tiers []AssetPlan `json:"tiers"`
DailyCreateLimit int64 `json:"dailyCreateLimit"`
CreatedToday int64 `json:"createdToday"`
Scope AssetScope `json:"scope"`
}
AssetQuota is the asset library plan, usage and limits.
type AssetScope ¶
type AssetScope struct {
WorkspaceID string `json:"workspaceId"`
Personal bool `json:"personal"`
Role string `json:"role"`
CanWrite bool `json:"canWrite"`
CanTopUp bool `json:"canTopUp"`
}
AssetScope says which library the key reaches and what it may do there.
type Balance ¶
type Balance struct {
// AvailableCredits is what a new task can draw on (rounded down to a whole
// credit), HeldCredits what accepted tasks have already reserved, and
// TotalCredits the two together.
AvailableCredits int64 `json:"availableCredits"`
HeldCredits int64 `json:"heldCredits"`
TotalCredits int64 `json:"totalCredits"`
// Available, Held and Total are the same amounts in USD.
Available string `json:"available"`
Held string `json:"held"`
Total string `json:"total"`
// Funding breaks the balance down by where the money came from. It is
// absent on accounts with nothing but a plain wallet.
Funding *FundingOverview `json:"funding,omitempty"`
}
Balance is what this account can spend right now, in credits and in USD decimal strings. Amounts are never floats: cents do not survive binary floating point.
type Character ¶
type Character struct {
ID string `json:"id"`
Kind string `json:"kind"`
Name string `json:"name"`
Note string `json:"note"`
CoverAssetID string `json:"coverAssetId"`
// Cover is nil while the character has no images.
Cover *Asset `json:"cover"`
// Assets are the character's images in order.
Assets []Asset `json:"assets"`
Count int `json:"count"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
Character is a named, ordered set of images in the asset library. It references those assets instead of copying them. Characters are created and edited on popnow.ai; the API only reads them.
type CharacterList ¶
type CharacterList struct {
Items []Character `json:"items"`
// NextCursor is absent on the last page.
NextCursor string `json:"nextCursor,omitempty"`
}
CharacterList is one page of characters.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client calls the PopNow API. It is safe for concurrent use.
func New ¶
New builds a client. It fails when no API key is available, because every endpoint needs one and discovering that at the first call is worse.
func (*Client) Assets ¶
Assets is EachAsset as a range-over-func iterator. A failed page yields one final (zero, err) pair and ends the sequence.
func (*Client) CommitUpload ¶
CommitUpload verifies the bytes against the ticket (size, media type, signature), copies them to an immutable private object and returns the popnow:// URI to put in task input. Repeating a successful commit is idempotent.
Code 40003 here means the stored file does not match what the ticket was issued for. Do not retry it: ask for a new ticket and upload again.
func (*Client) CreateAsset ¶
func (c *Client) CreateAsset(ctx context.Context, input CreateAssetInput, idempotencyKey string) (*Asset, error)
CreateAsset copies a file into the key's asset library and returns it with its stable popnow://a/ URI. Saving the same source again returns the existing asset without using more space.
idempotencyKey is required by the contract and bound to the request for 24 hours. Storage unlocks with the owner's lifetime top-ups: without one this returns 400 with APIError.Reason "asset_library_locked"; see GetAssetUsage.
func (*Client) CreateDownloadURL ¶
func (c *Client) CreateDownloadURL(ctx context.Context, taskID, key string) (*DownloadTicket, error)
CreateDownloadURL issues a fresh signed link to one output of a finished task. A finished task already carries usable links, so this is only needed once those have expired; polling the task again does the same job.
Leave key empty to select the first output. Do not attach your API key when fetching the link: it is a bearer grant on its own.
func (*Client) CreateTask ¶
func (c *Client) CreateTask( ctx context.Context, input CreateTaskInput, idempotencyKey string, opts CreateTaskOptions, ) (*CreateTaskResult, error)
CreateTask submits a generation and returns as soon as it is accepted.
Acceptance is not completion. The returned state is queued; read the outcome back with GetTask or WaitForTerminal. EstimatedCredits is held, not charged, and it is also the ceiling: the settled charge can only be lower.
idempotencyKey is required by this client even though the contract allows it to be omitted, because the automatic resend on a network fault is only sound when it exists. Without a key, a request that times out leaves you unable to tell an accepted task from a lost one, and the obvious reaction - send it again - is a second generation and a second charge. Reuse the same value for every attempt at the same logical submission: for 24 hours, and for this API key, a repeat returns the original task instead of creating another.
func (*Client) CreateUploadURL ¶
func (c *Client) CreateUploadURL(ctx context.Context, contentType string, size int64) (*UploadTicket, error)
CreateUploadURL asks for a presigned slot for exactly one file of exactly the declared size. Images are capped at 10 MiB, audio, video and documents at 90 MiB (audio and video at 600 seconds), LoRA weights at 2 GiB; the ticket's MaxBytes is the authoritative ceiling.
func (*Client) DeleteAsset ¶
DeleteAsset removes an asset from the library and every collection; its URI stops working in new requests. Deleting an unknown or already deleted asset returns the same result, so retries are safe.
func (*Client) EachAsset ¶
EachAsset walks every page of ListAssets and calls fn once per asset, repeating every filter and moving only the cursor. Return false from fn to stop early.
func (*Client) EachTask ¶
func (c *Client) EachTask(ctx context.Context, opts ListTasksOptions, fn func(TaskListItem) bool) error
EachTask walks every page of ListTasks and calls fn once per task. Return false from fn to stop early; the error is the first page that failed.
Every filter is repeated unchanged on each request and only the cursor moves: a cursor is only meaningful against the query it was issued for.
Pin From and To yourself for any walk that might cross midnight UTC. With them empty the server recomputes its default window on every page, so a walk that starts at 23:59 and continues at 00:01 splices two different answers together and nothing reports it.
func (*Client) GetAsset ¶
GetAsset reads one asset with a fresh private preview. An unknown asset and one this key's library does not hold return the same 404.
func (*Client) GetAssetUsage ¶
func (c *Client) GetAssetUsage(ctx context.Context) (*AssetQuota, error)
GetAssetUsage reports the asset library plan, how much is stored, its limits and what the next plan unlocks.
func (*Client) GetBalance ¶
GetBalance reads the available, held and total balance of this account. API calls and generations on popnow.ai share this one wallet.
func (*Client) GetCharacter ¶
GetCharacter reads one character with its images in order, each with a fresh private preview.
func (*Client) GetModel ¶
GetModel reads one model, always including its input schema.
A model identifier contains slashes, so it is escaped into a single path segment here; sending them raw would address a different resource.
func (*Client) GetTask ¶
GetTask reads one task created by this API key. An unknown task, another account's task and a sibling key's task are all a 404; the three cases are deliberately indistinguishable.
func (*Client) GetUsage ¶
func (c *Client) GetUsage(ctx context.Context, opts UsageOptions) (*UsageReport, error)
GetUsage reports this API key's settled spend and call counts.
Numbers are attributed to when a task was created, and settlement can land late, so a figure read today can still change tomorrow. This endpoint sits behind an account-wide bucket of thirty requests a minute shared by every key, and that limiter fails closed: follow tasks with GetTask or WaitForTerminal, never by polling usage.
func (*Client) ImportLoRA ¶
func (c *Client) ImportLoRA(ctx context.Context, input LoRAImport) (*UploadedFile, error)
ImportLoRA has the service download a .safetensors LoRA file once and store it as your upload, so weights behind a sign-in can be used without making them public. Put the returned URI in loras[].path.
The server downloads the whole file inside this call, so it gets the upload timeout rather than the ordinary request budget. It is not retried automatically: every successful call stores a new copy.
func (*Client) ListAssets ¶
ListAssets returns one page of the key's asset library. Listing and reading assets is limited to 60 requests per minute per account.
func (*Client) ListCharacters ¶
func (c *Client) ListCharacters(ctx context.Context, opts ListCharactersOptions) (*CharacterList, error)
ListCharacters returns one page of the characters in the key's asset library, newest first.
func (*Client) ListModels ¶
ListModels returns every image, video and audio model this API key may call, with its price in credits. The list is not paginated.
func (*Client) ListTasks ¶
ListTasks returns one page of this API key's task history, newest first.
The scope is this API key alone: tasks of sibling keys and generations started on the website are not here. Pages read live state, not a frozen snapshot.
func (*Client) PurgeTask ¶
PurgeTask destroys a finished task's stored content: the generated objects, the result payload and the request text including the prompt. The spend record stays.
The task ID is the idempotency key, so a repeat after a timeout is safe and returns the original PurgedAt. A queued or running task returns 400.
func (*Client) Quote ¶
Quote prices one exact request for five minutes without creating a task, holding credits or starting anything. It runs the same validation and admission rules as CreateTask, so it is also the cheapest way to find out that an input is wrong.
Pass the returned QuoteID and MaxCredits to CreateTask to refuse a price that moved in the meantime.
func (*Client) RetryTask ¶
func (c *Client) RetryTask(ctx context.Context, taskID, idempotencyKey string) (*RetryTaskResult, error)
RetryTask creates a new task from a failed or expired one. The source task is never modified, and the current schema, price, permissions and balance are all evaluated again, so a retry can cost a different amount or be refused.
The source must be terminal and settled; retrying too early returns 409, and retrying a succeeded task returns 404. Retention is inherited from the source. The idempotency key is scoped to the source task.
func (*Client) Tasks ¶
func (c *Client) Tasks(ctx context.Context, opts ListTasksOptions) iter.Seq2[TaskListItem, error]
Tasks is EachTask as a range-over-func iterator:
for task, err := range client.Tasks(ctx, popnow.ListTasksOptions{}) {
if err != nil {
return err
}
log.Println(task.TaskID)
}
A failed page yields one final (zero, err) pair and ends the sequence.
func (*Client) UpdateAsset ¶
func (c *Client) UpdateAsset(ctx context.Context, assetID string, input UpdateAssetInput) (*Asset, error)
UpdateAsset renames an asset or changes its favorite mark. The URI stays the same and nothing is copied. Repeating the same request is safe.
func (*Client) Upload ¶
func (c *Client) Upload(ctx context.Context, data []byte, contentType string) (*UploadedFile, error)
Upload runs all three steps for bytes already in memory: take a ticket, PUT the bytes straight into object storage, then commit. Only the URI on the returned file belongs in task input.
Uploading is free; you pay for the task that consumes the file. A public HTTPS URL can be passed in the input field directly and skips all of this.
func (*Client) UploadFile ¶
UploadFile streams a local file. An empty contentType is inferred from the extension, which only works for the accepted media types (.safetensors becomes ContentTypeLoRA).
func (*Client) UploadReader ¶
func (c *Client) UploadReader(ctx context.Context, r io.Reader, size int64, contentType string) (*UploadedFile, error)
UploadReader is Upload for a stream of a known size, so a large video or a LoRA file never has to sit in memory. r must yield exactly size bytes.
func (*Client) WaitForTerminal ¶
func (c *Client) WaitForTerminal(ctx context.Context, taskID string, opts WaitOptions) (*Task, error)
WaitForTerminal polls a task until it stops being queued or running, backing off between reads.
Terminal is defined as "neither of the two in-flight states" rather than as a list of the known endings, so a state added later ends the wait instead of extending it forever. Only "succeeded" carries a result.
A local timeout is not a verdict: it returns *WaitTimeoutError, and the generation is still running. Keep the task ID and reconcile it later, or use a callback URL instead of waiting.
type CreateAssetInput ¶
type CreateAssetInput struct {
Source string `json:"source"`
TaskID string `json:"taskId,omitempty"`
Key string `json:"key,omitempty"`
// Index is a pointer because 0, the first output, is a real value.
Index *int `json:"index,omitempty"`
FileID string `json:"fileId,omitempty"`
URL string `json:"url,omitempty"`
// Name defaults to "Saved asset" on the server; at most 80 characters.
Name string `json:"name,omitempty"`
}
CreateAssetInput chooses what to save. Set Source and the fields that go with it:
- AssetSourceTaskOutput: TaskID and either Key or Index (the zero-based position in output.assets). The task must be one of your own successful tasks.
- AssetSourceUpload: FileID of a committed upload (fil_... or popnow://f/fil_...).
- AssetSourceURL: URL, a public http or https address on port 80 or 443 without credentials. Only JPEG, PNG, WebP and GIF images, MP4 and WebM video and MP3, WAV, FLAC and Ogg audio are accepted.
type CreateTaskInput ¶
type CreateTaskInput struct {
// Model is an exact call name from the catalogue, in
// <publisher>/<model>/<task> form, for example
// "alibaba/z-image-turbo/text-to-image".
Model string `json:"model"`
// Input is validated against that model's input schema. Media fields take
// a public HTTPS URL, a committed popnow://f/ upload URI, a saved
// popnow://a/ asset URI, or a small Base64 image data URI.
Input map[string]any `json:"input"`
// CallBackURL is an optional public HTTPS endpoint for terminal delivery.
// Verify every delivery with VerifyWebhook before acting on it.
CallBackURL string `json:"callBackUrl,omitempty"`
// QuoteID together with ExpectedCredits (or ExpectedCost) confirms a
// price: if the price moved since the quote was signed, the task is
// rejected with code 40901 instead of being accepted at the new amount.
QuoteID string `json:"quoteId,omitempty"`
// ExpectedCredits is the confirmed number of credits to hold, as returned
// in Quote.MaxCredits. It is a pointer because 0 is a real amount. When
// both are set, ExpectedCredits wins over ExpectedCost.
ExpectedCredits *int64 `json:"expectedCredits,omitempty"`
// ExpectedCost is the same confirmation as a USD decimal string.
ExpectedCost string `json:"expectedCost,omitempty"`
}
CreateTaskInput is the body shared by Quote and CreateTask.
type CreateTaskOptions ¶
type CreateTaskOptions struct {
// RetentionSeconds, when non-nil, shortens how long this task's outputs
// and its stored request text are kept (sent as X-PopNow-Retention). It
// can only shorten: the effective value is the smallest of this, the
// account setting and the platform maximum, and it is reported back in
// Task.Retention.
//
// It is a pointer because 0 is a meaningful value - the outputs go as
// soon as the task reaches a terminal state - so it must not be what an
// empty options struct sends.
RetentionSeconds *int
// WaitSeconds, when non-nil, asks the service to hold the connection open
// for that many seconds waiting for the task to finish, so the common case
// needs no polling loop. Finishing inside the budget fills
// CreateTaskResult.Task with the complete record; running out of it
// returns the ordinary acceptance and you poll from there.
//
// It is an optimisation, not a guarantee. The value is sent exactly as
// given: the service clamps anything above its ceiling (60 today) and
// ignores, rather than rejects, a value that is not a positive integer.
// No ceiling is enforced here, because a copy of the platform's number is
// a constant that can go stale without anything saying so.
//
// Setting it also lifts this one request's own deadline to cover the wait.
WaitSeconds *int
}
CreateTaskOptions carries the optional header and query that accompany a submission.
type CreateTaskResult ¶
type CreateTaskResult struct {
TaskID string `json:"taskId"`
// State is queued for a new submission. An idempotent replay reports the
// original task's current state instead, which may already be terminal.
State string `json:"state"`
// EstimatedCredits is the number of credits held, and the ceiling on the
// final charge. EstimatedCost is the same amount in USD. Unused credits
// are released when the task settles.
//
// Both are zero when Task is non-nil: a task that already finished has a
// real cost instead of an estimate, in Task.Credits and Task.Cost.
EstimatedCredits int64 `json:"estimatedCredits"`
EstimatedCost string `json:"estimatedCost"`
// DeadlineAt is the server's execution deadline, fixed at acceptance. It
// is not a local wait bound and not an artefact expiry.
DeadlineAt time.Time `json:"deadlineAt"`
// Task is the complete terminal record, non-nil only when
// CreateTaskOptions.WaitSeconds was set and the task finished inside that
// budget. It is exactly what GetTask would return.
//
// Nil is the ordinary outcome and is not a failure: the wait ran out, the
// task is still running, and you poll from here. Code that reads Task
// without checking for nil works right up until the first slow generation.
Task *Task `json:"-"`
}
CreateTaskResult is the acceptance record, not a result.
func (*CreateTaskResult) InFlight ¶
func (r *CreateTaskResult) InFlight() bool
InFlight reports whether the task was still queued or running when the service produced this answer. Everything else is terminal, including a state this client has never heard of.
type CreditFacility ¶
type CreditFacility struct {
Enabled bool `json:"enabled"`
LimitUsd string `json:"limitUsd"`
AvailableUsd string `json:"availableUsd"`
UsedUsd string `json:"usedUsd"`
HeldUsd string `json:"heldUsd"`
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
Status string `json:"status"`
Version int64 `json:"version"`
}
CreditFacility is approved pay-later capacity. LimitUsd is a ceiling, not funds.
type DownloadTicket ¶
type DownloadTicket struct {
Key string `json:"key"`
URL string `json:"url"`
ExpiresAt time.Time `json:"expiresAt"`
}
DownloadTicket is a fresh signed link to one output of a finished task.
type EndpointEvent ¶
type EndpointEvent struct {
// ID is the webhook-id header. It stays the same across retries: use it
// to ignore duplicates.
ID string `json:"-"`
// Type is task.completed, task.succeeded, task.failed, task.expired or
// task.canceled. Treat the set as open.
Type string `json:"type"`
Data struct {
// Object is the task record at the moment the event was created.
Object *Task `json:"object"`
} `json:"data"`
// Raw is the verified body, for fields this struct does not name.
Raw json.RawMessage `json:"-"`
}
EndpointEvent is a verified account endpoint delivery.
func VerifyEndpointWebhook ¶
func VerifyEndpointWebhook(secret string, header http.Header, rawBody []byte, tolerance time.Duration) (*EndpointEvent, error)
VerifyEndpointWebhook authenticates one delivery to an account endpoint registered in the API console. These follow the Standard Webhooks format:
webhook-signature: v1,base64( HMAC-SHA256( key, "<webhook-id>.<webhook-timestamp>.<rawBody>" ) )
Two differences from VerifyWebhook: the raw body itself is signed (no SHA-256 step), and the HMAC key is the Base64-decoded part of the endpoint's whsec_ secret, not the secret string. During a secret rotation the header can hold several space-separated signatures; any match is accepted.
tolerance bounds the accepted clock skew; pass 0 for WebhookTolerance.
type FundingGrant ¶
type FundingGrant struct {
ID string `json:"id"`
Name string `json:"name"`
AmountUsd string `json:"amountUsd"`
AvailableUsd string `json:"availableUsd"`
HeldUsd string `json:"heldUsd"`
SpentUsd string `json:"spentUsd"`
Status string `json:"status"`
StartsAt time.Time `json:"startsAt"`
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
// ModelSlugs restricts this grant. Empty means every model, not none.
ModelSlugs []string `json:"modelSlugs,omitempty"`
CustomerMemo string `json:"customerMemo"`
CreatedAt time.Time `json:"createdAt"`
}
FundingGrant is one promotional grant.
type FundingOverview ¶
type FundingOverview struct {
// BalanceUsd is the net wallet balance. It can be negative.
BalanceUsd string `json:"balanceUsd"`
HeldUsd string `json:"heldUsd"`
// PrepaidAvailableUsd is unrestricted; GrantAvailableUsd may not pay for
// every model, which is decided when a task is admitted.
PrepaidAvailableUsd string `json:"prepaidAvailableUsd"`
GrantAvailableUsd string `json:"grantAvailableUsd"`
// CashShortfallUsd is a payment recovery gap. Non-zero blocks new tasks.
CashShortfallUsd string `json:"cashShortfallUsd"`
Credit *CreditFacility `json:"credit,omitempty"`
Grants []FundingGrant `json:"grants,omitempty"`
// GrantsHasMore says Grants was truncated.
GrantsHasMore bool `json:"grantsHasMore"`
}
FundingOverview separates the sources behind a balance, because they do not behave alike: a grant can be restricted to certain models and credit is an approved ceiling rather than money on hand.
type HTTPDoer ¶
HTTPDoer is the slice of *http.Client this package needs, so that tests can substitute a transport without a network.
type ListAssetsOptions ¶
type ListAssetsOptions struct {
// Kind is image, video or audio.
Kind string
// Sources limits the list to task_output, upload and/or url.
Sources []string
// Q is a case-insensitive substring of the asset name.
Q string
// Favorite, when non-nil, keeps only favorites (true) or only the rest.
Favorite *bool
// CollectionID keeps one collection of the same library.
CollectionID string
// EntityID keeps the images of one character.
EntityID string
// Sort is recent (the default), oldest, name or size. A cursor only works
// with the Sort it came from.
Sort string
// Cursor is the NextCursor of the previous page.
Cursor string
// Limit is the page size, 1 to 100.
Limit int
}
ListAssetsOptions filters and orders the asset library. A zero field means "do not filter".
type ListCharactersOptions ¶
type ListCharactersOptions struct {
// AssetID keeps only the characters that contain this asset.
AssetID string
Cursor string
// Limit is the page size, 1 to 100.
Limit int
}
ListCharactersOptions filters the character list.
type ListModelsOptions ¶
type ListModelsOptions struct {
// Modality is image, video or audio.
Modality string
// Provider is the model's creator.
Provider string
// Task is an exact task, for example text-to-image or image-to-video.
// "edit" and "image-to-image" select the same editing endpoints.
Task string
// Search matches names and descriptions.
Search string
// IncludeSchema adds each model's input JSON Schema. It makes the
// response much larger.
IncludeSchema bool
// IncludeExamples adds inputs that already validate against that schema,
// the fastest way to get a first call working.
IncludeExamples bool
}
ListModelsOptions filters the catalogue. A zero field means "do not filter".
type ListTasksOptions ¶
type ListTasksOptions struct {
// From and To are UTC dates in YYYY-MM-DD form and bound a half-open
// interval [From, To) of at most 92 days. Left empty, the server defaults
// To to tomorrow in UTC and From to seven days before it.
From string
To string
// State is one of the six task states.
State string
// Model is an exact catalogue identifier or a declared alias.
Model string
// Limit is the page size, 1 to 100, 20 by default on the server. The upper
// bound is deliberately not enforced here.
Limit int
// Cursor is the NextCursor of the previous page, passed through unchanged.
Cursor string
}
ListTasksOptions filters task history. A zero field means "do not filter".
type LoRAImport ¶
type LoRAImport struct {
// URL is a Hugging Face repo id (owner/repo, when the repo has exactly one
// .safetensors file), a Hugging Face repo or file page, or a direct https
// link to a .safetensors file.
URL string `json:"url"`
// Token is an optional Hugging Face access token or Civitai API key, used
// only for this download and never stored.
Token string `json:"token,omitempty"`
}
LoRAImport is the body of ImportLoRA.
type Model ¶
type Model struct {
// Model is the exact call name to pass as CreateTaskInput.Model, in
// <publisher>/<model>/<task> form.
Model string `json:"model"`
Family string `json:"family"`
// DisplayName is for your UI, not for calls.
DisplayName string `json:"displayName"`
// Provider is the model's creator, the lab that built it.
Provider string `json:"provider"`
// Modality is image, video or audio.
Modality string `json:"modality"`
// Tasks holds exactly one classification and is not always the suffix of
// Model: editing endpoints publish as .../edit and classify as
// image-to-image (or image-edit for single-purpose tools). Call with
// Model; group with Tasks. The set is open.
Tasks []string `json:"tasks"`
Async bool `json:"async"`
// Toolkit groups related endpoints for display (subject-swap,
// studio-tools). Empty when the model belongs to no toolkit.
Toolkit string `json:"toolkit,omitempty"`
// TaskTimeoutSeconds is the platform execution deadline, not the length
// of the media that comes out.
TaskTimeoutSeconds int `json:"taskTimeoutSeconds"`
// MaxOutputDurationSeconds is the longest output the schema allows, 0
// when the model declares none.
MaxOutputDurationSeconds int `json:"maxOutputDurationSeconds,omitempty"`
// Enabled and Available both have to be true before a model can be called.
Enabled bool `json:"enabled"`
Available bool `json:"available"`
// QuantityField names the input field that multiplies the price.
QuantityField string `json:"quantityField"`
Credits *ModelCredits `json:"credits,omitempty"`
// InputSchema is JSON Schema 2020-12 and stays raw on purpose: it is what
// validates your input and what a form generator renders. Request it with
// ListModelsOptions.IncludeSchema or read it from GetModel.
InputSchema json.RawMessage `json:"inputSchema,omitempty"`
// Version changes whenever the schema or metadata changes; cache on it.
Version string `json:"version"`
Availability string `json:"availability"`
Badges []string `json:"badges"`
RelatedModels []string `json:"relatedModels"`
Examples []ModelExample `json:"examples,omitempty"`
UpdatedAt time.Time `json:"updatedAt"`
}
Model is one callable endpoint from the catalogue.
type ModelCreditRule ¶
type ModelCreditRule struct {
When map[string]any `json:"when"`
PerUnit float64 `json:"perUnit"`
Unit string `json:"unit,omitempty"`
}
ModelCreditRule is one price tier: it applies when the request matches its When values, where "*" matches the field that sets the tier.
type ModelCredits ¶
type ModelCredits struct {
// Unit is how the price scales: per_image, per_second, per_request, ...
// Treat it as open.
Unit string `json:"unit"`
// PerUnit is the starting price per unit. It can be fractional.
PerUnit float64 `json:"perUnit"`
// Rules lists the price tiers.
Rules []ModelCreditRule `json:"rules,omitempty"`
}
ModelCredits is a model's price in credits (1 credit = $0.01), the same price popnow.ai shows. Use Quote for the exact amount of one request.
type ModelExample ¶
type ModelExample struct {
ID string `json:"id"`
Input map[string]any `json:"input"`
SortWeight int `json:"sortWeight"`
}
ModelExample is an input that validates against the model's current schema.
type Options ¶
type Options struct {
// APIKey defaults to the POPNOW_API_KEY environment variable. Keep it
// server-side; it is a bearer credential for your credit wallet.
APIKey string
// BaseURL defaults to APIBaseURL.
BaseURL string
// HTTPClient defaults to a client with no timeout of its own, because
// every request here already carries a context deadline.
HTTPClient HTTPDoer
// UserAgent replaces the default UserAgent. Anyone embedding this client
// in their own tool can name that tool here; keep it non-empty and
// specific, because generic default agents are refused at the edge.
UserAgent string
// AcceptLanguage, when set, is sent as Accept-Language to choose the
// language of error messages (for example "de" or "ja"). Codes never
// change with the language.
AcceptLanguage string
RequestTimeout time.Duration
UploadTimeout time.Duration
WaitTimeout time.Duration
// MaxRetries is the number of extra attempts a replayable request gets.
// Set it to -1 to disable retries entirely.
MaxRetries int
// Now and Rand exist for deterministic tests.
Now func() time.Time
Rand func() float64
// Sleep must return the context error if the context ends first.
Sleep func(ctx context.Context, d time.Duration) error
}
Options configures New. Every zero value falls back to a documented default.
type OutputAsset ¶
type OutputAsset struct {
// Key identifies this output for CreateDownloadURL and CreateAsset.
Key string `json:"key,omitempty"`
// URL is a signed link that works on its own. Never attach your API key to
// it: it is already a bearer grant. It is absent while Pending or
// Unavailable is true.
URL string `json:"url,omitempty"`
// ExpiresAt is this link's expiry, normally about twenty minutes out. It
// is not the retention deadline of the result; poll again for a fresh link.
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
MIME string `json:"mime,omitempty"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
DurationSeconds float64 `json:"durationSeconds,omitempty"`
Bytes int64 `json:"bytes,omitempty"`
// Role is video, image, audio or a model-specific role such as segment.
Role string `json:"role,omitempty"`
// Pending means the artefact is still being copied into storage.
Pending bool `json:"pending,omitempty"`
Unavailable bool `json:"unavailable,omitempty"`
// Deleted means the owner permanently deleted this one output. It always
// comes with Unavailable and no Key or URL.
Deleted bool `json:"deleted,omitempty"`
// Layer is set only on the outputs of a layer-decomposition task.
Layer *AssetLayer `json:"layer,omitempty"`
}
OutputAsset is one generated artefact of a task.
type PurgeResult ¶
type PurgeResult struct {
TaskID string `json:"taskId"`
// ContentState is purged after a successful destroy and on every repeat.
// Still present means nothing was removed and the call may be retried.
ContentState string `json:"contentState"`
// PurgedAt is the time of the first destroy, not of this call.
PurgedAt *time.Time `json:"purgedAt,omitempty"`
ContentRemovedBy string `json:"contentRemovedBy,omitempty"`
// BillingRetained is always true: destroying content never touches the
// ledger or the charged amount.
BillingRetained bool `json:"billingRetained"`
// MediaDeletionPending is true while the background sweep is still
// removing the stored objects, which takes about a minute.
MediaDeletionPending bool `json:"mediaDeletionPending"`
}
PurgeResult reports what PurgeTask destroyed.
type Quote ¶
type Quote struct {
// QuoteID goes back to CreateTask together with MaxCredits.
QuoteID string `json:"quoteId"`
// Model is the resolved endpoint identifier.
Model string `json:"model"`
// MaxCredits is the most this request can cost, in credits. It is held
// when the task is accepted. Pass it back as ExpectedCredits.
MaxCredits int64 `json:"maxCredits"`
// ExpectedCredits is what the request usually costs. It equals MaxCredits
// unless the final charge depends on the output, and the charge never
// exceeds MaxCredits.
ExpectedCredits int64 `json:"expectedCredits"`
// EstimatedCost and MaxCharge are the same amounts as USD decimal strings.
EstimatedCost string `json:"estimatedCost"`
MaxCharge string `json:"maxCharge"`
Currency string `json:"currency"`
// Quantity and Unit explain how the amount was reached.
Quantity string `json:"quantity"`
Unit string `json:"unit"`
ExpiresAt time.Time `json:"expiresAt"`
}
Quote is a signed price for one exact request. It reserves nothing and guarantees no capacity; it only fixes the price for five minutes.
type RetryTaskResult ¶
type RetryTaskResult struct {
CreateTaskResult
SourceTaskID string `json:"sourceTaskId"`
}
RetryTaskResult additionally names the task this one was created from.
type Task ¶
type Task struct {
TaskID string `json:"taskId"`
// SourceTaskID is set when this task came from RetryTask.
SourceTaskID string `json:"sourceTaskId,omitempty"`
Model string `json:"model"`
State string `json:"state"`
// Input is the normalized input, including the random seed the service
// picked when you omitted one. Omitted once retention has redacted it.
Input map[string]any `json:"input,omitempty"`
Output *TaskOutput `json:"output,omitempty"`
// ErrorCode is one of the nine ErrorCode constants. Branch on it.
ErrorCode string `json:"errorCode,omitempty"`
// ErrorMessage explains the failure. Do not parse it.
ErrorMessage string `json:"errorMessage,omitempty"`
// Credits and Cost are the same amount: the final charge once Settled is
// true, and the held estimate before that. A failed or expired task
// normally releases its hold in full; read them once Settled is true
// instead of assuming.
Credits int64 `json:"credits"`
Cost string `json:"cost"`
Settled bool `json:"settled"`
CreatedAt time.Time `json:"createdAt"`
DeadlineAt *time.Time `json:"deadlineAt,omitempty"`
CompletedAt *time.Time `json:"completedAt,omitempty"`
ContentState string `json:"contentState,omitempty"`
ContentRemovedBy string `json:"contentRemovedBy,omitempty"`
PurgedAt *time.Time `json:"purgedAt,omitempty"`
Retention *TaskRetention `json:"retention,omitempty"`
}
Task is the full record returned by GetTask. Terminal and non-terminal states share this shape.
func (*Task) HasPendingAssets ¶
HasPendingAssets reports whether an artefact is still being copied. A task can be succeeded with its files not yet linkable.
func (*Task) InFlight ¶
InFlight reports whether the task is still queued or running. Everything else is terminal, including a state this client has never heard of.
func (*Task) ReadyAssets ¶
func (t *Task) ReadyAssets() []OutputAsset
ReadyAssets returns the artefacts that have a usable URL right now.
type TaskList ¶
type TaskList struct {
Items []TaskListItem `json:"items"`
HasMore bool `json:"hasMore"`
// NextCursor is present only when HasMore is true. It is opaque.
NextCursor string `json:"nextCursor,omitempty"`
}
TaskList is one page of task history.
type TaskListItem ¶
type TaskListItem struct {
TaskID string `json:"taskId"`
// RequestID is the X-Request-Id of the request that created the task.
RequestID string `json:"requestId,omitempty"`
Model string `json:"model"`
State string `json:"state"`
// Credits and Cost are the final charge once Settled is true, and the
// held estimate before that.
Credits int64 `json:"credits"`
Cost string `json:"cost"`
Settled bool `json:"settled"`
CreatedAt time.Time `json:"createdAt"`
DeadlineAt time.Time `json:"deadlineAt"`
// CompletedAt is nil while the task is still in flight.
CompletedAt *time.Time `json:"completedAt,omitempty"`
}
TaskListItem is one row of task history: metadata only, with no input, output or signed media URL. Read a selected task with GetTask.
type TaskOutput ¶
type TaskOutput struct {
// Text is the plain-text result. For a multichannel transcription it holds
// one line per channel, in channel order.
Text string `json:"text,omitempty"`
// Transcript is the structured speech-to-text result. Nil on every other
// task.
Transcript *TaskTranscript `json:"transcript,omitempty"`
Assets []OutputAsset `json:"assets,omitempty"`
}
TaskOutput is the result payload of a finished task.
Not every endpoint produces files. Transcription answers in Text and returns no assets, so check what you actually got before reaching for Assets[0].
type TaskRetention ¶
type TaskRetention struct {
OutputsExpireAt time.Time `json:"outputsExpireAt"`
PromptsExpireAt time.Time `json:"promptsExpireAt"`
Source string `json:"source"`
}
TaskRetention says when this task's content disappears and which layer decided it: header, account or platform.
type TaskTranscript ¶
type TaskTranscript struct {
Text string `json:"text,omitempty"`
// Language is the detected language as the model reports it ("en");
// LanguageCode is the ISO 639-3 form ("eng") when known.
Language string `json:"language,omitempty"`
LanguageCode string `json:"languageCode,omitempty"`
// LanguageProbability is a pointer because "not reported" and "zero
// confidence" are different.
LanguageProbability *float64 `json:"languageProbability,omitempty"`
DurationSeconds float64 `json:"durationSeconds,omitempty"`
Words []TaskTranscriptWord `json:"words,omitempty"`
Channels []TaskTranscriptChannel `json:"channels,omitempty"`
}
TaskTranscript is a structured transcription.
type TaskTranscriptChannel ¶
type TaskTranscriptChannel struct {
Index int `json:"index"`
Text string `json:"text,omitempty"`
Language string `json:"language,omitempty"`
LanguageCode string `json:"languageCode,omitempty"`
Words []TaskTranscriptWord `json:"words,omitempty"`
}
TaskTranscriptChannel is the transcript of one audio channel.
type TaskTranscriptWord ¶
type TaskTranscriptWord struct {
Text string `json:"text"`
Start float64 `json:"start"`
End float64 `json:"end"`
Type string `json:"type,omitempty"`
SpeakerID string `json:"speakerId,omitempty"`
}
TaskTranscriptWord is one timed segment of a transcript, in seconds.
type UpdateAssetInput ¶
type UpdateAssetInput struct {
Name *string `json:"name,omitempty"`
Favorite *bool `json:"favorite,omitempty"`
}
UpdateAssetInput renames an asset or marks it as a favorite. Only what is set changes; both are pointers so that "leave alone" and "clear" differ.
type UploadTicket ¶
type UploadTicket struct {
FileID string `json:"fileId"`
// Key is a compatibility alias of the final URI and is unusable until the
// commit succeeds. Put UploadedFile.URI in task input, never this.
Key string `json:"key"`
UploadURL string `json:"uploadUrl"`
Method string `json:"method"`
// Headers must be forwarded to the PUT exactly as given.
Headers map[string]string `json:"headers"`
// ExpiresAt covers the PUT and the commit together: about twenty minutes,
// two hours for LoRA weights. Ask for a ticket right before uploading.
ExpiresAt time.Time `json:"expiresAt"`
// MaxBytes is the authoritative ceiling for this upload.
MaxBytes int64 `json:"maxBytes"`
}
UploadTicket is a presigned slot for exactly one file of a declared size.
type UploadedFile ¶
type UploadedFile struct {
FileID string `json:"fileId"`
Status string `json:"status"`
Bytes int64 `json:"bytes"`
// ContentType is what the server verified.
ContentType string `json:"contentType"`
SHA256 string `json:"sha256"`
// URI is the popnow://f/fil_... value to put in task input (or in
// loras[].path for LoRA weights). Use this one, not the upload URL, not a
// storage key, not the bare file ID.
URI string `json:"uri"`
ExpiresAt time.Time `json:"expiresAt"`
// DurationSeconds is a decimal string, present for audio and video.
DurationSeconds string `json:"durationSeconds,omitempty"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
}
UploadedFile is a committed file, ready to be referenced by a task.
type UsageDay ¶
type UsageDay struct {
Day string `json:"day"`
Calls int64 `json:"calls"`
Succeeded int64 `json:"succeeded"`
Failed int64 `json:"failed"`
Credits int64 `json:"credits"`
Spend string `json:"spend"`
}
UsageDay is one day of activity. Day is a UTC calendar date, YYYY-MM-DD.
type UsageModel ¶
type UsageModel struct {
Model string `json:"model"`
Calls int64 `json:"calls"`
Succeeded int64 `json:"succeeded"`
Failed int64 `json:"failed"`
Credits int64 `json:"credits"`
Spend string `json:"spend"`
}
UsageModel is one model's share of the same window.
type UsageOptions ¶
UsageOptions bounds a usage query to a half-open UTC interval [From, To) of at most 92 days, both as YYYY-MM-DD. Left empty the server uses the last seven days.
type UsageReport ¶
type UsageReport struct {
// From and To echo the window actually used. Read them rather than
// assuming your own: left empty, the server picks.
From string `json:"from"`
To string `json:"to"`
Currency string `json:"currency"`
// TotalCredits and TotalSpend count settled charges only, never holds.
TotalCalls int64 `json:"totalCalls"`
TotalCredits int64 `json:"totalCredits"`
TotalSpend string `json:"totalSpend"`
// Days and Models are sparse: a day or a model with no activity is absent.
Days []UsageDay `json:"days"`
Models []UsageModel `json:"models"`
}
UsageReport is settled spend and call counts over one window.
type WaitOptions ¶
type WaitOptions struct {
// Timeout is the local bound on the whole wait, DefaultWaitTimeout by
// default. It is not a service level.
Timeout time.Duration
// Interval is the first gap between polls, DefaultPollInterval by default.
Interval time.Duration
// MaxInterval caps how far that gap grows, MaxPollInterval by default.
MaxInterval time.Duration
// AcceptPendingAssets returns a terminal task even when its artefacts are
// still being copied, so they have no URL yet. Leave it false to get back
// a record you can actually download from.
AcceptPendingAssets bool
}
WaitOptions tunes WaitForTerminal. Every zero field takes its default.
type WaitTimeoutError ¶
WaitTimeoutError means a local polling deadline elapsed. It is not evidence that the task failed: the generation is still running somewhere, so store TaskID and reconcile it later instead of submitting the work twice.
func (*WaitTimeoutError) Error ¶
func (e *WaitTimeoutError) Error() string
func (*WaitTimeoutError) Unwrap ¶
func (e *WaitTimeoutError) Unwrap() error
Unwrap lets errors.Is(err, context.DeadlineExceeded) recognise this.
type WebhookDelivery ¶
type WebhookDelivery struct {
// TaskID is the task the signature was computed over. Use this one, not a
// value you read out of the body yourself.
TaskID string
// DeliveryID is the envelope's request_id. It stays the same across
// retries: store it with a unique index and ignore repeats.
DeliveryID string
// PayloadVersion is the X-Webhook-Payload-Version header, "2" for every
// current integration.
PayloadVersion string
// Task is the task record carried by a version 2 delivery, the same shape
// GetTask returns. Nil for version 1. Its signed links expire after about
// twenty minutes; store the task ID, not the link.
Task *Task
}
WebhookDelivery is a verified task callback.
func VerifyWebhook ¶
func VerifyWebhook(secret string, header http.Header, rawBody []byte, tolerance time.Duration) (*WebhookDelivery, error)
VerifyWebhook authenticates one callBackUrl delivery (a task callback).
The signature is
base64( HMAC-SHA256( secret, "<taskId>.<timestamp>.<sha256_hex(rawBody)>" ) )
where secret is the account's webhook signing secret (POPNOW_WEBHOOK_SECRET in the API console), timestamp is X-Webhook-Timestamp, and taskId comes from data.taskId in payload version 2 and from top-level task_id in the older version 1, as X-Webhook-Payload-Version says.
rawBody must be the exact bytes that arrived. Decoding and re-encoding the JSON first changes key order, spacing and escaping, and the digest then fails every time for a reason that looks like a wrong secret.
tolerance bounds the accepted clock skew; pass 0 for WebhookTolerance. Every retry is signed again with a fresh timestamp.