Convilyn developers

Types

Pydantic data models returned by SDK methods. Treat them as read-only — they carry data, not behaviour.

File

The record returned by client.files.upload. Represents an uploaded artifact on the backend; pass it into job-creation methods rather than re-uploading.

class File:
    file_id: str
    filename: str
    size: int                    # bytes
    content_type: str
    created_at: datetime
    job_id: str | None
    is_input: bool

ConvertJob

The lifecycle handle for a turbo-lane conversion job. Returned by client.convert.create / create_and_wait.

class ConvertJob:
    job_id: str
    status: Literal["queued", "processing", "completed", "failed"]
    processor_type: str
    progress: int                       # 0–100
    progress_message: str | None
    result_files: list[ResultFile] | None
    error: JobError | None
    retry_count: int                    # default 0
    created_at: datetime
    updated_at: datetime
    started_at: datetime | None
    completed_at: datetime | None
    estimated_duration: int | None      # seconds, when known

    @property
    def is_terminal(self) -> bool       # True when status is "completed" or "failed"

Convention follows OpenAI / Stripe: the model carries data only; behaviour lives in the resource class. Use methods on client.convert to act on a ConvertJob (poll, download, etc.).

ResultFile

A single output file produced by a completed job.

class ResultFile:
    filename: str
    size: int
    mimetype: str
    url: str                     # presigned download URL

JobError

Structured error envelope attached to a failed job. Mirrors the wire format.

class JobError:
    code: str
    message: str

GoalJob

Lifecycle handle for a goal-lane (multi-step workflow) job.

class GoalJob:
    job_spec_id: str
    status: Literal[                         # full goal-lane lifecycle (13 states)
        # non-terminal
        "draft", "created", "analyzing", "slots_pending",
        "ready", "ready_with_preview", "confirmed", "queued", "executing",
        # terminal
        "completed", "partial", "failed", "cancelled",
    ]
    progress: int                            # 0–100, default 0
    item_version: int | None
    attempt_id: str | None
    goal_text: str | None
    workflow_id: str | None
    file_ids: list[str]
    pending_slots: list[PendingSlot]         # populated when status == "slots_pending"
    filled_slots: dict[str, object]
    pending_interrupts: list[dict[str, object]]   # opaque HITL union (slot_fill / batch_review / …)
    agent_message: str | None
    error_message: str | None
    error_code: str | None
    created_at: datetime
    updated_at: datetime
    started_at: datetime | None
    completed_at: datetime | None

    @property
    def is_terminal(self) -> bool     # status in {completed, partial, failed, cancelled}

    @property
    def needs_input(self) -> bool     # status == "slots_pending"

GoalJob carries no result_files field — produced artifacts are surfaced through the goal-lane event stream and the workflow's own outputs, not on the job handle.

GoalEvent

A single event from a goal-lane event stream (received via the WebSocket transport when iterating client.goals.events(job_spec_id) — async only).

class GoalEvent:
    type: Literal[                       # 13 event types
        "tool_started", "tool_finished",
        "agent_step_started", "agent_step_finished",
        "orchestration_transition",
        "status", "progress", "completed", "failed",
        "slot_needed", "keepalive",
        "agent_text", "agent_text_done",
    ]
    schema_version: int
    job_spec_id: str
    emitted_at: datetime
    seq: int                             # default 0
    data: dict[str, Any]

    @property
    def is_terminal(self) -> bool        # type in {completed, failed, cancelled}

PendingSlot

A human-in-the-loop interrupt point on a goal-lane job. When a workflow reaches a slot, it pauses and surfaces the question through pending_slots on the GoalJob.

class PendingSlot:
    slot_id: str
    slot_type: str
    question: str
    options: list[str | dict[str, str]] | None   # null when free-text
    required: bool                                # default True
    is_disambiguation: bool                       # default False
    suggested_value: object | None
    suggested_confidence: float | None

Resolve a slot by calling client.goals.fill_slot(job_spec_id, slot_id=..., value=...).

Community workflow models

Returned by client.workflows.* (marketplace surface).

Workflow

Full detail row for one workflow.

class Workflow:
    workflow_id: str
    owner_id: str | None
    spec_id: str                 # use as `source_spec_id` when forking
    source_spec_id: str | None
    source_type: str | None      # "curated" | "user" | "blank"
    name: str
    description: str | None
    visibility: Literal["private", "public", "archived"]
    tags: list[str]
    stats: WorkflowStats
    item_version: int            # use for optimistic-lock PATCH

WorkflowSummary

Light-weight community-list row (no system_prompt / spec_json). Returned in pages from client.workflows.search.

WorkflowSearchPage

class WorkflowSearchPage:
    items: list[WorkflowSummary]
    cursor: str | None           # pass to next search() call

WorkflowStats

class WorkflowStats:
    run_count: int
    fork_count: int
    like_count: int

LikeResponse

Returned by client.workflows.like(workflow_id) (toggle).

class LikeResponse:
    liked: bool                  # post-call state
    like_count: int

Billing / quota models

Returned by client.account.*.

Plan

class Plan:
    tier: Literal["free", "pro"]

CostEstimate

Full response from client.account.get_quota(tools=..., max_iterations=...).

class CostEstimate:
    estimated_micro_u: int           # legacy single-number summary (back-compat)
    estimated_usd: float             # rough USD equivalent
    estimated_total_micro_u: int     # prefer this for the projected total
    estimated_min_micro_u: int       # range floor
    estimated_max_micro_u: int       # range ceiling
    tools: list[ToolCostEstimate]
    quota_check: QuotaCheck          # verdict against your tier

Prefer the explicit estimated_min_micro_u / estimated_total_micro_u / estimated_max_micro_u triple over the legacy estimated_micro_u summary.

QuotaCheck

class QuotaCheck:
    state: Literal["ok", "soft_limit", "quota_exceeded"]
    tier: Literal["free", "pro"]
    estimated_micro_u: int
    threshold_micro_u: int
    upgrade_url: str | None      # in-app pricing CTA when not "ok"

ToolCostEstimate

Per-tool row inside CostEstimate.tools.

class ToolCostEstimate:
    tool_name: str
    per_invocation_micro_u: int

Type usage notes

  • All models are Pydantic — .model_dump() gives a JSON-safe dict, .model_dump_json() gives a string.
  • Fields typed as datetime are timezone-aware (UTC) and serialize as ISO 8601.
  • Every model is frozen=True (immutable) — you can't reassign fields on a returned instance.
  • The marketplace, billing, and GoalEvent models add extra="allow" for forward-compat — when the backend adds a new wire field, your existing code keeps working. The core job/file models (File, ResultFile, JobError, ConvertJob, PendingSlot, GoalJob) do not, so an unexpected field there is a hard validation error rather than silently tolerated.
  • The SDK does not mutate returned models. If you need to retain a snapshot, store the model itself (not a derived dict).
  • PEP 561: the SDK ships a py.typed marker, so mypy / pyright / pylance pick up these types automatically.