@reviseio/api
v0.4.1
Published
Async TypeScript client for the Revise prompt and file conversion API
Downloads
820
Maintainers
Readme
@reviseio/api
A dependency-free, typed client for Revise's prompt and conversion API. Node.js 22+ or compatible runtimes with native fetch, FormData, Blob, Web Crypto and AbortSignal.any. Keep API keys on your server.
npm install @reviseio/apiimport { ReviseClient, Source } from "@reviseio/api";
const revise = new ReviseClient({ apiKey: process.env.REVISE_API_KEY! });
const contract = Source.fromUrl("https://example.com/contract.docx");
const edited = await revise.edit(
contract,
"Change the payment term to 30 days.",
);
const pdf = await revise.convert(edited, "pdf");
const bytes = await pdf.bytes(); // ready to store, attach, or send in an HTTP responseedit and convert handle source retrieval, upload, submission, and polling. Each returns a new Source; bytes download only when needed. Edits select the clean DOCX. Use { trackedChanges: true } to select tracked changes, or edited.variant('tracked_changes') to access the other artifact from the same request. source.result returns a defensive copy of the job receipt, including usage and partial-completion indicators; source.artifact exposes the selected artifact metadata.
Sources
| Factory | Input |
| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Source.fromBytes(bytes, { filename }) | Uint8Array, Node Buffer, or ArrayBuffer; snapshots exactly the selected bytes. |
| Source.fromUrl(url, options?) | Remote HTTP(S) URL, including signed URLs; supports source-specific headers and fetch. |
| Source.fromPath(path, options?) | Local file, read lazily; Node only. |
| Source.fromBlob(blob, options?) | Blob or native File; a File supplies its filename. |
| Source.fromStream(streamOrFactory, { filename }) | Binary Web stream or async iterable, including Node Readable. A factory can open a fresh stream after a failed read. |
| Source.fromResponse(response, options?) | An existing fetch Response. |
| Source.fromText(text, options?) | UTF-8 text; defaults to document.txt. Set filename for Markdown/HTML. |
| Source.fromFileId(id, options?) | Existing Revise upload; honors its server retention/reuse rules. |
| Source.fromArtifact(artifact, { client }?) | Artifact metadata from an earlier request. Returned sources are already bound to their client. |
All factories accept optional filename, contentType, maxBytes, and upload lifetime. Filename determines input format; MIME is advisory. URL/Response sources infer filenames from Content-Disposition, then the URL path. Supply a filename for opaque URLs or unnamed Blobs. Conversion outputs are docx, pdf, md, html, txt; the server rejects unsupported pairs and same-format conversion.
import { ReviseClient, Source } from "@reviseio/api";
const revise = new ReviseClient({ apiKey: process.env.REVISE_API_KEY! });
const pdf = await revise.convert(
Source.fromText("# Quarterly report", { filename: "report.md" }),
"pdf",
);
const blob = await pdf.blob();
const stream = await pdf.stream(); // fresh Web stream of buffered, verified bytes
await pdf.save("/tmp/report.pdf"); // optional explicit Node filesystem outputByte sources are buffered once and reusable. bytes() returns an independent copy; blob() shares an immutable snapshot. stream() buffers and verifies before returning a fresh stream, so it is not a bounded-memory passthrough. Upload resolution enforces 18 MiB; standalone reads default to 64 MiB and accept { maxBytes, signal }. Failed one-shot streams/Responses cannot resume; create a new source or supply a stream factory. The active reader owns the signal and limit: if it aborts or exceeds its limit, every concurrent reader of that one-shot source receives the stored failure. Use a stream factory when independent retry is needed. Successful URL/path/factory reads are cached and do not reread changing content.
Eligible artifacts pass directly into another edit unless that edit requests encrypted output; encrypted-output edits download, verify, and reupload the plaintext first. Conversion requires a file ID, so the client downloads and reuploads artifact sources automatically. Uploaded file IDs can be submitted but cannot be read through bytes() because the API has no original-file download endpoint. Cross-client artifacts download through their bound owner before uploading to the destination; bound file IDs cannot transfer this way. Unbound IDs use the receiving client, and callers are responsible for their ownership.
URL downloads use their own credentials and transport, never the configured Revise API key. Redirects are disabled. Applications accepting user-supplied URLs must enforce their own network access policy; the client does not block private network addresses.
For text-only or read/comment prompts, use revise.prompts.run(body), which returns the full receipt. Low-level endpoint methods remain available below. promptFile(file, body) and convertFile(file, body) retain eager upload/job/download workflows for callers needing receipt-and-bytes bundles.
Configuration
import { ReviseClient } from "@reviseio/api";
const revise = new ReviseClient({
apiKey: process.env.REVISE_API_KEY!,
baseUrl: "http://127.0.0.1:3110", // default: https://revise.io/api; omit /v1
pollIntervalMs: 1_000,
timeoutMs: 1_000_000,
maxRetries: 2,
maxRetryDelayMs: 60_000,
// fetch: yourFetch,
});Every endpoint accepts { signal, idempotencyKey } as its final options argument. Wait/run/file helpers and edit/convert additionally accept timeoutMs, pollIntervalMs, and synchronous onProgress. Low-level downloads and eager file helpers accept verify: false to opt out of the default byte-length and SHA-256 checks. artifacts.content(id) returns a raw Response for streaming; the caller owns consuming/closing it and verifying bytes.
The edit/convert timeout covers input resolution, upload, retries and polling. This includes waiting for an asynchronous stream factory, even if it ignores the signal. Streams returned after cancellation are closed; a later read can retry a factory source. Returned sources are lazy: read them with their own { signal } to bound later downloads. Eager file helpers include output download in their total timeout. Source reads always verify artifact length and SHA-256. A server Retry-After can lengthen polling intervals. Individual endpoint calls use the caller's signal; constructor timeout defaults apply to workflow methods. Aborting local work does not cancel the server job. Use explicit prompts.cancel(id) or conversions.cancel(id).
HTTP 429/502/503/504 responses receive bounded retries on GET, DELETE, and idempotency-backed POST endpoints, honoring Retry-After. Cancel/resume and transport exceptions are not automatically retried. Set maxRetries: 0 to disable retries. Failed responses are consumed before backoff; artifact downloads are sequential per file workflow.
Recovery and errors
import {
ReviseClient,
ReviseWorkflowError,
ReviseApiError,
} from "@reviseio/api";
const revise = new ReviseClient({ apiKey: process.env.REVISE_API_KEY! });
try {
await revise.prompts.run(
{ prompt: "Draft an introduction." },
{
idempotencyKey: "introduction-42",
signal: AbortSignal.timeout(60_000),
onProgress: (state) => console.log(state.jobId ?? "Awaiting submission"),
},
);
} catch (error) {
if (error instanceof ReviseWorkflowError) {
// Persist recovery securely. It retains the base key, job key, known IDs,
// stage, and latest receipt, even if a download failed after success.
console.log(error.recovery.jobId, error.recovery.stage);
if (error.cause instanceof ReviseApiError) console.log(error.cause.status);
}
throw error;
}Reuse your own key and identical input to replay an operation across calls. edit, convert, and file helpers derive :upload and :prompt/:convert keys; base keys may be at most 192 printable ASCII characters without spaces. Ordinary keyed endpoints allow 200, webhook create/retry allow 128. When a job ID is known, use get/wait or retry its downloads instead of creating another billable job. onProgress is synchronous, runs before I/O and at transitions, and exposes generated keys for persistence. Use low-level methods if your application must await durable storage between submission steps.
wait returns paused/failed/cancelled jobs for inspection. Run/file helpers throw ReviseJobError (a ReviseWorkflowError with .result) for those outcomes. Successful partial work remains successful: inspect incomplete and stop_reason. ReviseApiError exposes .status, .code, .body, .headers; high-level workflow errors preserve it as .cause. ReviseArtifactError exposes metadata for an integrity failure. getResponseMetadata(result) returns status, headers, Location and idempotent-replay information for a JSON result without changing its shape.
Encryption and retention
Pass outputEncryption: { format: 'jwe', public_key_pem: '...' } in edit/convert options, or output_encryption in low-level request bodies. Other high-level options include inference, limits, metadata, retention, and edit-only responseOptions. The returned source reads and verifies JWE ciphertext, with encryption metadata. It rejects using encrypted artifacts as inputs until you decrypt locally and create a new byte source. Encrypted sources expose application/jose and a .jwe filename, even with a plaintext MIME override. The original format/MIME remains in artifact metadata. It does not generate keys or decrypt. Keep the RSA private key local; only an SPKI public PEM is sent. Encrypted output cannot be directly continued/reused and should not be written as an ordinary DOCX/PDF. Other languages must authenticate JWE (RSA-OAEP-256, A256GCM) and its job/artifact context before exposing plaintext.
Uploads default to ephemeral and follow server retention rules. Use lifetime: 'persistent' for reusable templates, and delete explicitly when finished. Errors do not auto-delete sources because a server job may still need them. Content deletion preserves billing receipts. If using retention.delete_after_webhook_id, download and durably store results before acknowledging the completion webhook with 2xx.
Endpoint methods
All JSON fields follow the public API. The package exports PromptRequest, Prompt, ConversionRequest, Conversion, Artifact, UploadedFile, options/error types, and generated components/paths types.
| Resource | Methods |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| files | upload(file), get(id), delete(id) |
| prompts | create(body), get(id), list(query?), cancel(id), resume(id, body), deleteContent(id), wait(idOrJob), run(body) |
| conversions | create(body), get(id), list(query?), cancel(id), deleteContent(id), wait(idOrJob), run(body) |
| artifacts | get(id), content(id), download(idOrArtifact) |
| account | get(), ledger(), topup(id) |
| models | list(query?) |
| usage | get(query?) |
| webhooks | create(body), list(), delete(id), deliveries(id, query?), retry(id, deliveryId) |
List responses preserve the server page/cursor. Supply cursor: page.next_cursor with the same filters for the next page. Metadata filters use objects ({ metadata: { customer: 'Acme' } }); dates use RFC3339 strings. Undefined/null query values are omitted. Console identity/funding mutations are outside this API-key client.
Ledger entries contain nullable prompt_id and conversion_id. Use the populated field to retrieve the matching resource; funding entries have neither. Conversion billing units, quantities, and rates survive content deletion, webhook acknowledgement, and expiry.
0.4.1
Asynchronous Source.fromStream factories now respect workflow deadlines even when they ignore the supplied signal. Streams returned after cancellation are closed, and a later read can retry the factory. Ledger types expose nullable conversion_id alongside prompt_id so conversion entries link to the correct resource. The updated documentation covers signed artifact downloads, stable billing after content deletion, and PDF validation errors.
Upgrading to 0.4.0
Artifact downloads now follow the API's signed storage redirect without forwarding the API key to storage. Source.bytes(), artifacts.download(), and file helpers continue to check the downloaded byte count and SHA-256. Other API requests and remote source downloads still reject redirects.
Conversion billing details in usage.conversion now use unit (conversion, page, or historical word), quantity, and unit_price_usd, with minimum_fee_usd optional for historical word pricing. Replace reads of output_words and rate_usd_per_1000_words with these fields. Historical receipts keep their admitted pricing; clients must not recalculate them using current rates.
All conversion HTTP routes use /v1/convert, as they have since 0.3.0. The TypeScript resource remains client.conversions, and the high-level helper remains client.convert(source, format).
Monorepo development
The cross-language guide is docs/Revise API Client Spec.md in the monorepo. This package imports no editor/backend/private SDK code. Types are generated from the checked-in OpenAPI contract with defaultable request fields kept optional. Tests detect schema drift and compile minimal requests.
cd packages/api
npm ci
npm run generate
npm test
npm pack
# npm publish --access public # when release is authorized; prepublishOnly runs testsPrompt-server builds use its own installed TypeScript compiler for this local file dependency, so no separate package install is needed to build it. Prompt-server npm test also runs this package's tests, bootstrapping missing test tools. test:api:live runs test files serially against an already running API/worker:
cd backend/prompt-server
PROMPT_API_LIVE_EVALS=1 PROMPT_API_BASE_URL=http://127.0.0.1:3110 \
PROMPT_API_KEY=... npm run test:api:livePROMPT_API_LIVE_CASE selects prompt cases. Conversion coverage includes all five output formats, DOCX roundtrip, invalid same-format rejection, encrypted prompt/conversion output, and a remote-source edit/edit/conversion chain with artifact reuse. Tests verify artifact integrity and authenticate/decrypt JWE locally. An explicit run ID replays ordinary identical cases; encrypted tests include the newly generated public-key fingerprint because changing that key changes the request.
