Lolly
打开应用

Automate with the Work API

备注: Lolly Work

This guide covers the optional organisation service. Configuration, console and API paths refer to your Work instance. The packaged console keeps its release-matched copy of this guide.

Your Work server can render on-brand files for a script, a pipeline or an agent, with the same tools, design system and policy the Lolly apps use. This page takes you from a token to a finished file, then covers the rest of the automation surface: schemas, the document verbs, background jobs, batches and provider assets.

Every other route the console, the lw CLI and the apps call is in the Lolly Work API reference.

Render your first file

  • Get a token. An owner mints one for the script: lw tokens create --label ci --role admin. The secret, lwt_…, is shown once.
  • Ask what the token may do. GET /api/v1/discovery/me answers with the formats this server renders and whether the token may render at all.
  • Read the tool's inputs. GET /api/v1/schema/qr-code returns the inputs this caller may set, after policy and design-system rules.
  • Render. POST /api/v1/render with the tool, the format and the inputs returns the file.
export LW_TOKEN=lwt_...
curl -H "Authorization: Bearer $LW_TOKEN" https://work.example.com/api/v1/discovery/me
curl -H "Authorization: Bearer $LW_TOKEN" -H 'content-type: application/json' \
  -d '{"toolId":"qr-code","format":"svg","inputs":{"url":"https://example.com"}}' \
  https://work.example.com/api/v1/render > qr.svg

The answer is the file itself, with its media type, an x-lolly-cache-key header and an x-lolly-brand-check header (see design-system rules). Add ?async=1 to get a job instead, which you can poll or have the server call you back about. Every error reads { "error": { "code", "message" } }.

From a token to a file

  1. 1Get a tokenlw tokens create

    An owner mints it once. Send it as Bearer lwt_…

  2. 2Ask what it may doGET /api/v1/discovery/me

    Formats this server renders, and whether the token may render

  3. 3Read the inputsGET /api/v1/schema/:toolId

    Hidden, locked and choice inputs already applied

  4. 4RenderPOST /api/v1/render

    { toolId, format, inputs }

Step 4 answers in one of two ways

The file, straight away

200 with the bytes, their media type and an x-lolly-brand-check header

A job, with ?async=1
  • 202 { jobId, statusUrl }
  • read the result with the x-lw-job-token header
  • or take a signed callback at notify.webhook.url
  • or deliver it to a destination by reference

The path a script takes: a token, a check of what the token may do and which inputs it may set, then a render that answers with the file or with a job.

signed by Lollywork-automation-api-journey.htmlHTMLpixels, not shapes9 KBAI generatedgenerated by Claude Opus 5.5自己验证获取已签名文件

Before you start: a token and a permission

The token. Service tokens are minted by owners (token.manage), from the console or lw tokens create --label <name> --role <role> [--days <n>]. The label says which automation holds the token, up to 80 characters. The role is any role but guest. A token lasts 90 days unless you choose 1 to 365. Send it as Authorization: Bearer lwt_…; the lw CLI reads it from LW_TOKEN or --token. The routes are in service tokens.

The permission. Rendering on the server needs tool.use for the tool and export.server. Admins hold export.server by default and a grant can give it to anyone else, so a token with the member role cannot render until someone grants that permission. The other verbs need tool.use for any tool they name.

What a token is. A token acts as its own account, with its role and no groups. Its jobs and file references belong to the token, not to the owner who minted the token. A token is refused wherever a person has to decide or act, such as approvals, deliveries to a destination that needs approval and live collaboration. Who can call what lists every credential.

On an open instance, a caller with no credential may also use and render any tool that no visibility rule hides. On a gated instance, every route on this page except the public GET /api/v1/discovery answers 401 without a credential; a job's result token counts as one for that job's result.

Find out what you can do

RouteWhoWhat it answers
GET /api/v1/discoverypublicWhat this deployment can do, the same for everyone: document API and engine versions, server render formats, hooked tools, Content Credentials, automation verbs and batch limits, live collaboration, document agents, whether file references are on (files.references), and unavailable entries for what it lacks. Shared caches may hold it; it carries an ETag
GET /api/v1/discovery/memember or service token; anyone on an instance that is not gatedWhat the caller can do: principal (member, service or guest), can bits for tool.use, export.server, export.request, delivery.create and catalog.read, provider reference schemes that resolve for the caller, limits, the render worker's live readiness when one is configured, and unavailable entries. Private, never cached
GET /api/v1/discovery/tools?limit=&cursor=as aboveThe tools the caller can use, sorted by id, 25 a page by default and at most 100. Each entry has name, version, renderer, the server formats the caller may request, can.render, its schema URL and unavailable entries. next is an opaque cursor, or null on the last page
GET /api/v1/schema/:toolId?view=&format=tool.useThe caller's effective input schema (below). view=raw returns the tool's own schema and needs policy.edit. format selects the output format whose design-system rules apply. Without it, the schema carries only the rules that apply to every format

Discovery lives under /api/v1 rather than /.well-known/, because on a shared origin /.well-known/ belongs to the web shell, whose static /.well-known/lolly.json describes the shell.

Read a tool's effective schema

GET /api/v1/schema/:toolId answers with apiVersion and schema. The schema is the effective one for this caller, built from the tool, the tool policy that applies to the caller, the design-system choice rules for the format and the deployment's render probe:

The ETag header moves with any change to the effective contract, so If-None-Match answers 304 while the contract holds. A script can cache the schema and re-check it for the price of one header.

The rest of the schema response

The response adds view, toolId, tool (name, version), versions (engine, tool, policy, design, catalog, and rules when design-system rules apply), policy, render (renderer, formats, and format when one was requested), unavailable and etag.

Each policy entry is one change the policy made: an effect of locked, choice or format-excluded with the input or format, plus the overlay's name as by and the rule's reason when they exist. Entries never show groups or values from rules that do not apply to the caller.

Each unavailable entry, here and in discovery, identifies one format, action or feature with a stable code and a reason:

CodeMeaning
WORKER_NOT_CONFIGUREDthe format needs the Chromium render worker, and this deployment does not run one
UNSUPPORTED_FORMATthe deployment does not produce this format on the server; the web app can still export that format
HOOKED_TOOL_NEEDS_CHROMIUMthe tool runs scripts, and no worker is configured
TOOL_REQUIRES_UNMETthe tool needs host APIs the in-process render host does not provide
SVG_TEMPLATE_REQUIREDserver rendering needs an SVG template
FORMAT_NOT_ALLOWEDtool policy does not allow the format
FORBIDDENthe caller lacks the permission, such as export.server for render
RENDER_BUSY / WORKER_UNREACHABLEthe configured worker is at capacity or did not answer its readiness check
TOOL_UNAVAILABLEthe deployment cannot load the tool

Render and the other document verbs

The automation surface mirrors the open-source engine's document verbs:

RouteWhat it does
POST /api/v1/renderrenders a file: { toolId, format, inputs }. format is required (400 INVALID_INPUT without one). Needs export.server
POST /api/v1/compileturns { toolId, inputs } into a document, with locked values applied
POST /api/v1/validatechecks inputs and reports problems as errors, without rendering
POST /api/v1/inspectdescribes a file (sent as below) or a document
POST /api/v1/measuremeasures a document, sent as document or as { toolId, inputs }
POST /api/v1/optimizeshrinks a file (sent as below), or optimizes a document
POST /api/v1/diffcompares two documents, a and b
POST /api/v1/packagepacks a compiled document, or { toolId, inputs }, into a .lolly file

Inputs travel as JSON in inputs, not as URL merge fields. Schema, compile, package and render all enforce the caller's tool.use decision and the tool-visibility overlay, so a document verb cannot be used to discover a tool hidden from that caller.

Policy. Compile, validate, package and the rows of a batch check inputs against the same policy the schema shows:

Design-system rules

Design-system rules can apply to some output formats only. Compile, validate and package take an optional format, one the tool declares, and then apply that format's rules, as the schema read with the same format shows. Without one they apply only the rules every format shares. A batch row is checked against the rules for the batch's format, and a render against the rules for its own format.

Rules that are not choices, such as a text length, are not in the schema. The render checks them and refuses a value that breaks one with BRAND_RULE_VIOLATION. A render's x-lolly-brand-check header reports the check, and durable output metadata carries brandRules with its disposition, scope and revision. A passing check is not a review of how the output looks. Policy editors publish the rules through managed rule mappings.

Files in, files out

/inspect and /optimize take the file in one of four ways:

/optimize with output: "file" keeps its output as a new file reference of the caller and answers { file, bytes, savedBytes, stages } instead of bytesBase64.

Render from a URL

GET /render/<toolId>.<format>?<inputs> renders with the inputs in the query, the way a Lolly share link carries them. It suits an image tag or a script with no JSON body.

Who may call it: a member who may use the tool and holds export.server; a guest whose link opens that tool; or, on an open instance, anyone without signing in, for a tool visible to everyone. A service token cannot call this route, because it reads only a session or a guest link; a script with a token uses POST /api/v1/render. A query that is not in canonical order is answered 308 to the canonical one.

For worker-backed renders, asset inputs use the same URL encoding as the Lolly shell. A user/team/<fileId> reference is resolved to that project's file endpoint only when the submitter can read the project and the file belongs to the render's scoped read ticket. The worker receives no general session credentials. Private files outside that scope stay out of reach; inline baked assets keep their original bytes.

Run work in the background

Any document verb, render or package takes ?async=1, or Prefer: respond-async, and answers 202 { jobId, statusUrl } with a Location header.

RouteWhat it does
GET /api/v1/jobsthe caller's jobs
GET /api/v1/jobs/:idone job: its state, progress, resultSha256 (computed from the output bytes, null until a result exists), resultToken and callback
GET /api/v1/jobs/:id/resultthe finished result
DELETE /api/v1/jobs/:idremoves queued work or retained output; 409 JOB_OUTPUT_IN_USE while a delivery retains that output
POST /api/v1/jobs/:id/deliveriessends a completed render to an outbound destination without a download and upload round trip; the delivery shares the immutable result and records the job id

Jobs belong to the member or service token that created them. Repeating a request with the same Idempotency-Key returns its existing job; reusing that key for different request bytes returns 409 IDEMPOTENCY_KEY_REUSED.

Callbacks

A job can call you back when it settles. callbackUrl is used only when it exactly matches the instance's own webhook endpoint, notify.webhook.url (configuration). Each callback carries x-lolly-timestamp and an x-lolly-signature header signed with LW_WEBHOOK_SECRET, and its body is { jobId, state, resultUrl, resultToken, deliveryId }.

resultToken is a 24-hour token for that one job's result. Send it as the x-lw-job-token header on GET /api/v1/jobs/:id/result, and the receiver needs no session of its own. A completed job resource carries the same resultToken, and null before then.

备注: The token in the result URL is deprecated

The token used to travel in the URL, as resultUrl?token=…, and a URL ends up in access, proxy and CDN logs. In Work 1.1.2 resultUrl still carries ?token= and the result route still accepts the query token, answering with a Deprecation header (RFC 9745). A later release drops the query from resultUrl and refuses a query token, so move receivers to the header now.

Callbacks go through a durable outbox. The write that settles a job also stores its callback, and any Work process sends callbacks that are due, so a callback left unanswered by a restart is sent again afterwards.

Every attempt for one job carries the same id, in x-lolly-delivery-id and as deliveryId in the signed body. Act on each id once: that is the idempotency contract of a signed-HTTPS delivery. Each attempt has a fresh timestamp, signature, result URL and result token. A refusal, a timeout or a dropped connection is retried after 5 s, 30 s, 2 min, 10 min and 30 min, six attempts in all.

The job reports callback: { id, state, attempts, lastOutcome, lastStatus, nextAttemptAt }:

Make many files at once

POST /api/v1/batch { toolId, format, rows, keepGoing?, retries?, concurrency?, priority? } always creates a job and produces one ZIP. Requested concurrency is capped at four in this process, priority at 0 to 9 and retries at three. Progress is exposed as { done, total }, and the ZIP's manifest.json records every row outcome.

In place of rows, bind: { source, query?, as? } reads JSON or CSV from a governed provider:

For batches whose rows each need their own durable record, use render batches instead.

Assets from providers

Provider references are resolved during compile, before the render cache identity is worked out:

Both remote kinds are bounded in time and bytes, content-addressed, and pass through immutable resize, format-conversion and metadata-strip stages.

Durable renders

POST /api/v1/renders submits a persistent single-tool request that survives a restart. List or get its state, retrieve its verified output, cancel it, or create a retry through the same API. The standalone server recovers expired leases; function-only hosts refuse new submissions. Recoverable renders has the request contract, routes, limits and deployment requirements.

Limits and errors

The automation routes share the rateLimit.automation bucket, per client IP: discovery, schemas, the document verbs, renders, batches and jobs, GET /render/…, agent connections and the outbound delivery POSTs. That includes the public GET /api/v1/discovery, so reading it never costs a sign-in attempt. The bucket is admission control, not pricing: Work keeps no per-principal quota or usage account for these routes. Sizes are in configuration.

Errors keep the { "error": { "code", "message" } } shape. These are the codes an automation script meets most; the common codes cover sign-in, permissions and links.

CodeStatusMeaning
INVALID_INPUT400a required field is missing or malformed
DOCUMENT_API_ERROR400the compile, inspect, diff, measure, optimize or package operation could not be performed
DATA_BINDING_ERROR400a live JSON or CSV provider binding could not resolve, parse, query or validate
UNSUPPORTED_FORMAT400a format this deployment cannot produce (org-config's render.formats lists what it can)
FORMAT_NOT_ALLOWED403the format exists here, but this tool's overlay policy excludes that format
INPUT_LOCKED422a locked input was supplied by the caller
INPUT_HIDDEN422the caller supplied an input that policy hides from them
INPUT_NOT_ALLOWED422the value is not one of those a choice rule allows
BRAND_RULE_VIOLATION422a render input breaks a reviewed design-system rule for that format
ROW_VALIDATION_FAILED422a batch row does not satisfy the selected tool contract
IDEMPOTENCY_KEY_REUSED409the caller reused an idempotency key for different request bytes
HOOKED_TOOL_NEEDS_CHROMIUM501the tool runs scripts and no worker is configured (org-config's render.hookedTools is false)
TOOL_REQUIRES_UNMET501the manifest's requires lists a host.* API the in-process render host lacks (text, compose, audio, the device APIs); route the tool through the Chromium worker
RENDER_BUSY503the render worker is at capacity; retry after Retry-After seconds