{"id":51429474,"url":"https://github.com/bnomei/cloudconvert-sdk","last_synced_at":"2026-07-05T03:02:04.494Z","repository":{"id":362108954,"uuid":"1256985311","full_name":"bnomei/cloudconvert-sdk","owner":"bnomei","description":"Async Rust SDK primitives for the CloudConvert","archived":false,"fork":false,"pushed_at":"2026-06-26T21:26:01.000Z","size":253,"stargazers_count":0,"open_issues_count":6,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-27T00:17:37.278Z","etag":null,"topics":["async","cloudconvert","convert","document-conversion","file-conversion","oauth2","pdf","socket-io","tokio-rs","webhooks","websocket"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/cloudconvert-sdk","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bnomei.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-02T09:00:04.000Z","updated_at":"2026-06-17T16:15:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bnomei/cloudconvert-sdk","commit_stats":null,"previous_names":["bnomei/cloudconvert-sdk"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/bnomei/cloudconvert-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bnomei%2Fcloudconvert-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bnomei%2Fcloudconvert-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bnomei%2Fcloudconvert-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bnomei%2Fcloudconvert-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bnomei","download_url":"https://codeload.github.com/bnomei/cloudconvert-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bnomei%2Fcloudconvert-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35141966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","cloudconvert","convert","document-conversion","file-conversion","oauth2","pdf","socket-io","tokio-rs","webhooks","websocket"],"created_at":"2026-07-05T03:02:03.560Z","updated_at":"2026-07-05T03:02:04.475Z","avatar_url":"https://github.com/bnomei.png","language":"Rust","funding_links":["https://www.buymeacoffee.com/bnomei"],"categories":[],"sub_categories":[],"readme":"# cloudconvert-sdk\n\n[![Crates.io Version](https://img.shields.io/crates/v/cloudconvert-sdk)](https://crates.io/crates/cloudconvert-sdk)\n[![Docs.rs](https://img.shields.io/docsrs/cloudconvert-sdk)](https://docs.rs/cloudconvert-sdk)\n[![CI](https://img.shields.io/github/actions/workflow/status/bnomei/cloudconvert-sdk/ci.yml?branch=main)](https://github.com/bnomei/cloudconvert-sdk/actions/workflows/ci.yml)\n[![Crates.io Downloads](https://img.shields.io/crates/d/cloudconvert-sdk)](https://crates.io/crates/cloudconvert-sdk)\n[![License](https://img.shields.io/crates/l/cloudconvert-sdk)](https://crates.io/crates/cloudconvert-sdk)\n[![Discord](https://flat.badgen.net/badge/discord/bnomei?color=7289da\u0026icon=discord\u0026label)](https://discordapp.com/users/bnomei)\n[![Buymecoffee](https://flat.badgen.net/badge/icon/donate?icon=buymeacoffee\u0026color=FF813F\u0026label)](https://www.buymeacoffee.com/bnomei)\n\nAsync Rust SDK primitives for the [CloudConvert](https://cloudconvert.com)\nAPI v2.\n\n`cloudconvert-sdk` gives Tokio applications typed CloudConvert clients, job and\ntask builders, upload and download helpers, operation metadata validation,\nOAuth 2.0 flows, webhook signature helpers, signed job URLs, and optional retry\nand Socket.IO support.\n\nThis is an unofficial library. Use the\n[official CloudConvert API documentation](https://cloudconvert.com/docs) and the\n[CloudConvert Job Builder](https://cloudconvert.com/job-builder) as the source\nof truth for service behavior, scopes, formats, engines, regions, sandbox usage,\nand operation-specific options.\n\n## Requirements\n\n- Rust 1.87 or newer.\n- Tokio for async API calls.\n- A CloudConvert API key for live API calls. Payload builders, examples, and\n  most tests run offline without credentials.\n\n## Installation\n\nAdd the crate to your application:\n\n```toml\n[dependencies]\ncloudconvert-sdk = \"0.3\"\ntokio = { version = \"1\", features = [\"macros\", \"rt-multi-thread\"] }\n```\n\nThe crate is a library only. It does not install a binary.\n\nDefault features are empty. Enable optional features only when you need them:\n\n```toml\ncloudconvert-sdk = { version = \"0.3\", features = [\"retry\", \"socket\"] }\n```\n\n| Feature | Adds |\n| --- | --- |\n| `retry` | `RetryPolicy` and automatic retry for idempotent CloudConvert API requests. |\n| `socket` | Socket.IO clients, streams, and managed socket wait helpers. |\n\n## Configure the client\n\nCreate an API key in the\n[CloudConvert dashboard](https://cloudconvert.com/dashboard/api/v2/keys), then\nstore it outside your source code:\n\n```sh\nexport CLOUDCONVERT_API_KEY=\u003cAPI_KEY\u003e\n```\n\nBuild a client from the environment:\n\n```rust\nuse cloudconvert_sdk::{ApiKey, CloudConvertClient};\n\nlet client = CloudConvertClient::builder(ApiKey::from_env()?).build()?;\n```\n\nThe builder also supports sandbox mode, regional production hosts, custom base\nURLs, custom `reqwest` clients, and transport timeouts:\n\n```rust\nuse std::time::Duration;\n\nuse cloudconvert_sdk::{ApiKey, CloudConvertClient, Region, TransportConfig};\n\nlet client = CloudConvertClient::builder(ApiKey::from_env()?)\n    .region(Region::EuCentral)\n    .transport_config(\n        TransportConfig::default()\n            .connect_timeout(Duration::from_secs(10))\n            .request_timeout(Duration::from_secs(120))\n            .user_agent(\"my-app/1.0\"),\n    )\n    .build()?;\n```\n\nUse `.sandbox(true)` to route requests to CloudConvert sandbox hosts. Use\n`Region::EuCentral`, `Region::UsEast`, or `Region::Custom(...)` for regional\nproduction API hosts. Use `.with_base_urls(...)` for tests, proxies, or\nnon-standard deployments.\n\nSecret-bearing types such as `ApiKey`, `OAuthAccessToken`, `OAuthRefreshToken`,\n`OAuthClientSecret`, and `SigningSecret` redact their `Debug` output.\n\n## Quick start\n\nThis example creates a URL import job, converts a DOCX file to PDF, waits for\nthe job to finish, and downloads each `export/url` result.\n\n```rust\nuse cloudconvert_sdk::{ApiKey, CloudConvertClient, FileExtension, JobCreateRequest};\n\n#[tokio::main]\nasync fn main() -\u003e cloudconvert_sdk::Result\u003c()\u003e {\n    let client = CloudConvertClient::builder(ApiKey::from_env()?).build()?;\n\n    let request = JobCreateRequest::linear()\n        .import_url(\"https://example.test/input.docx\")\n        .convert(FileExtension::Pdf)?\n        .export_url()?\n        .build();\n\n    let job = client.jobs().create(request).await?;\n    let finished = client.jobs().wait(\u0026job.id).await?;\n\n    for file in finished.export_urls() {\n        if let Some(url) = \u0026file.url {\n            let bytes = client.download(url).await?;\n            println!(\"downloaded {} bytes as {}\", bytes.len(), file.filename);\n        }\n    }\n\n    Ok(())\n}\n```\n\nExpected result:\n\n```txt\ndownloaded \u003cbytes\u003e bytes as \u003cfilename\u003e\n```\n\nFor production workflows, prefer CloudConvert webhooks or Socket.IO waits over\nlong blocking waits.\n\n## Build jobs\n\nCloudConvert jobs serialize tasks as an object keyed by task name. The SDK\ngenerates task names unless you provide explicit names.\n\nUse `JobCreateRequest::linear()` when every task feeds into the next task:\n\n```rust\nuse cloudconvert_sdk::{FileExtension, JobCreateRequest};\n\nfn build_request() -\u003e cloudconvert_sdk::Result\u003cJobCreateRequest\u003e {\n    let request = JobCreateRequest::linear()\n        .import_url(\"https://example.test/input.docx\")\n        .convert(FileExtension::Pdf)?\n        .export_url()?\n        .build();\n\n    Ok(request)\n}\n```\n\nLinear methods that infer their input from the previous task return\n`Error::InvalidBuilderState` when they are called before a source task exists.\n\nUse `*_with(...)` methods when a serial task needs options:\n\n```rust\nuse cloudconvert_sdk::{FileExtension, JobCreateRequest};\n\nfn build_request() -\u003e cloudconvert_sdk::Result\u003cJobCreateRequest\u003e {\n    let request = JobCreateRequest::linear()\n        .import_url_with(\"https://example.test/input.docx\", |task| {\n            task.filename(\"input.docx\")\n        })\n        .convert_with(FileExtension::Pdf, |task| {\n            task.input_format(FileExtension::Docx)\n                .engine(\"office\")\n                .filename(\"converted.pdf\")\n        })?\n        .export_url_with(|task| task.inline(false))?\n        .build();\n\n    Ok(request)\n}\n```\n\nUse `JobCreateRequest::graph(|job| ...)` when a job branches, joins multiple\ninputs, or references a non-adjacent task. Each graph method returns a\n`TaskName` handle:\n\n```rust\nuse cloudconvert_sdk::{FileExtension, JobCreateRequest};\n\nlet request = JobCreateRequest::graph(|job| {\n    let source = job.import_url(\"https://example.test/input.docx\");\n    let pdf = job.convert(\u0026source, FileExtension::Pdf);\n    let png = job.convert(\u0026source, FileExtension::Png);\n    job.export_url([\u0026pdf, \u0026png]);\n})\n.tag(\"branch-demo\")\n.build();\n```\n\n`TaskName` handles also work for multi-input operations such as `merge`,\nwatermarks that use an imported image file, and `export/url` tasks that archive\nmultiple outputs:\n\n```rust\nuse cloudconvert_sdk::{\n    FileExtension, JobCreateRequest, Layer, PositionHorizontal, PositionVertical,\n};\n\nlet request = JobCreateRequest::graph(|job| {\n    let cover_docx = job.import_url_with(\"https://example.test/report-cover.docx\", |task| {\n        task.filename(\"cover.docx\")\n    });\n    let body_docx = job.import_url_with(\"https://example.test/report-body.docx\", |task| {\n        task.filename(\"body.docx\")\n    });\n    let logo_png = job.import_url_with(\"https://example.test/logo.png\", |task| {\n        task.filename(\"logo.png\")\n    });\n\n    let cover_pdf = job.convert_with(\u0026cover_docx, FileExtension::Pdf, |task| {\n        task.input_format(FileExtension::Docx).filename(\"cover.pdf\")\n    });\n    let body_pdf = job.convert_with(\u0026body_docx, FileExtension::Pdf, |task| {\n        task.input_format(FileExtension::Docx).filename(\"body.pdf\")\n    });\n    let merged = job.merge_with([\u0026cover_pdf, \u0026body_pdf], FileExtension::Pdf, |task| {\n        task.filename(\"report.pdf\")\n    });\n    let watermarked = job.watermark_image_with(\u0026merged, \u0026logo_png, |task| {\n        task.input_format(FileExtension::Pdf)\n            .layer(Layer::Above)\n            .image_width(180)\n            .position(PositionVertical::Bottom, PositionHorizontal::Right)\n            .margins(24, 24)\n            .opacity(80)\n            .filename(\"report-watermarked.pdf\")\n    });\n\n    job.export_url_with([\u0026cover_pdf, \u0026body_pdf, \u0026watermarked], |task| {\n        task.archive_multiple_files(true)\n    });\n})\n.tag(\"report-package\")\n.build();\n```\n\nWhen the task name itself matters, use `JobBuilder::task(...)`,\n`JobBuilder::add_named_task(...)`, or `JobGraphBuilder::add_named_task(...)`.\nFor operations not yet typed by this SDK, use `TaskRequest::custom(...)`.\n\nTyped builders cover imports from URL, upload, base64, raw content, S3, Azure\nBlob, Google Cloud Storage, OpenStack, and SFTP; conversion, optimization,\nwatermarking, website capture, thumbnails, metadata, metadata writes, merge,\narchive, command, and PDF tasks; and exports to URL, S3, Azure Blob, Google\nCloud Storage, OpenStack, SFTP, and upload callbacks.\n\n## File extensions\n\nUse `FileExtension` for known CloudConvert format tokens:\n\n```rust\nuse cloudconvert_sdk::{ConvertTask, FileExtension};\n\nlet task = ConvertTask::new(\"upload-file\", FileExtension::Pdf)\n    .input_format(FileExtension::Docx);\n```\n\nFormat setters still accept strings for forward compatibility. Strings are\nnormalized by trimming leading dots and lowercasing ASCII, so `.PDF` and `PDF`\nserialize as `pdf`.\n\n## Upload and download files\n\nUse `import/upload` when your application already has the input file locally.\nThe job creation response contains the signed upload form; the SDK handles the\nmultipart upload helper.\n\n```rust\nuse std::path::Path;\n\nuse cloudconvert_sdk::{ApiKey, CloudConvertClient, FileExtension, JobCreateRequest};\n\nasync fn run() -\u003e cloudconvert_sdk::Result\u003c()\u003e {\n    let client = CloudConvertClient::builder(ApiKey::from_env()?).build()?;\n    let request = JobCreateRequest::linear()\n        .import_upload()\n        .convert_with(FileExtension::Pdf, |task| {\n            task.input_format(FileExtension::Txt)\n        })?\n        .export_url()?\n        .build();\n\n    let job = client.jobs().create(request).await?;\n    let upload_task_id = job\n        .tasks\n        .iter()\n        .find(|task| task.operation == \"import/upload\")\n        .and_then(|task| task.id.as_deref())\n        .expect(\"import/upload task should have an id\");\n\n    let upload_task = client.tasks().get(upload_task_id).await?;\n    client.upload_path(\u0026upload_task, \"input.txt\").await?;\n\n    let finished = client.jobs().wait(\u0026job.id).await?;\n    tokio::fs::create_dir_all(\"downloads\").await?;\n\n    for file in finished.export_urls() {\n        if let Some(url) = \u0026file.url {\n            let filename = Path::new(\u0026file.filename)\n                .file_name()\n                .and_then(|name| name.to_str())\n                .unwrap_or(\"download\");\n            client.download_to_path(url, Path::new(\"downloads\").join(filename)).await?;\n        }\n    }\n\n    Ok(())\n}\n```\n\nDownload helpers never attach CloudConvert bearer credentials to signed storage\nURLs. Upload helpers submit to the signed form action returned by CloudConvert.\n\n## API overview\n\nThe crate exports typed resource clients from `CloudConvertClient`:\n\n| Client method | Purpose |\n| --- | --- |\n| `jobs()` | Create, list, fetch, wait for, redirect, and delete jobs. |\n| `tasks()` | Create standalone tasks, list, fetch, wait for, cancel, retry, and delete tasks. |\n| `operations()` | List operation metadata, options, and engine versions; validate task payloads against returned metadata. |\n| `users()` | Read the authenticated user and, with `socket`, open user-scoped event streams. |\n| `webhooks()` | Create, list, and delete webhooks. |\n\nUseful helpers:\n\n- `download(...)`, `download_stream(...)`, and `download_to_path(...)`.\n- `upload_bytes(...)`, `upload_body(...)`, `upload_stream(...)`, and\n  `upload_path(...)`.\n- `sign_payload(...)` and `verify_signature(...)` for webhook signatures.\n- `sign_job_url(...)` for signed job-template URLs.\n- `socket_base_url(...)`, `SocketChannel`, `JobSocketEvent`, and\n  `TaskSocketEvent` for Socket.IO payloads.\n\n## OAuth 2.0\n\nUse API keys for server-side integrations owned by one CloudConvert account. Use\nOAuth when your app acts on behalf of CloudConvert users.\n\n```rust\nuse cloudconvert_sdk::{\n    JobListQuery, OAuthClient, OAuthClientSecret, OAuthScope,\n};\n\nasync fn run() -\u003e cloudconvert_sdk::Result\u003c()\u003e {\n    let oauth = OAuthClient::new(\"client-id\", OAuthClientSecret::new(\"client-secret\"))?;\n    let redirect = oauth.authorization_code_url_with_state(\n        \"https://app.example.test/cloudconvert/callback\",\n        [OAuthScope::TaskRead, OAuthScope::TaskWrite],\n        \"state-from-your-app\",\n    )?;\n\n    // Redirect the user to `redirect`, then exchange the returned code.\n    let token = oauth\n        .exchange_code(\"authorization-code\", \"https://app.example.test/cloudconvert/callback\")\n        .await?;\n    let client = token.into_client_builder().build()?;\n\n    let _jobs = client.jobs().list(\u0026JobListQuery::default()).await?;\n    Ok(())\n}\n```\n\n`OAuthAccessToken::from_env()`, `OAuthRefreshToken::from_env()`, and\n`OAuthClientSecret::from_env()` read `CLOUDCONVERT_OAUTH_ACCESS_TOKEN`,\n`CLOUDCONVERT_OAUTH_REFRESH_TOKEN`, and `CLOUDCONVERT_OAUTH_CLIENT_SECRET`.\nOAuth-backed clients use the same SDK resources and Socket.IO helpers as\nAPI-key clients.\n\n## Webhooks and signed job URLs\n\nUse `verify_signature(...)` to check inbound CloudConvert webhook payloads with\nyour webhook signing secret:\n\n```rust\nuse cloudconvert_sdk::{SigningSecret, verify_signature};\n\nlet valid = verify_signature(\n    br#\"{\"event\":\"job.finished\"}\"#,\n    \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\n    \u0026SigningSecret::new(\"webhook-signing-secret\"),\n)?;\n```\n\nUse `sign_payload(...)` for tests that need CloudConvert-compatible webhook\nsignatures. Use `sign_job_url(...)` when you embed a signed job template into a\nhosted CloudConvert job URL.\n\n## Operation metadata\n\nFor metadata-driven integrations, call `operations().list(...)` with\n`include_options()` or `include_options_and_engine_versions()`:\n\n```rust\nuse cloudconvert_sdk::{ConvertTask, FileExtension, OperationListQuery, TaskRequest};\n\nasync fn validate(client: cloudconvert_sdk::CloudConvertClient) -\u003e cloudconvert_sdk::Result\u003c()\u003e {\n    let operation = client.operations().list(\n        \u0026OperationListQuery::default()\n            .operation(\"convert\")\n            .input_format(\"docx\")\n            .output_format(\"pdf\")\n            .include_options_and_engine_versions(),\n    ).await?.remove(0);\n\n    let task = TaskRequest::from(\n        ConvertTask::new(\"import-file\", FileExtension::Pdf)\n            .input_format(FileExtension::Docx),\n    );\n    operation.validate_task(\u0026task).expect(\"task should match operation metadata\");\n    Ok(())\n}\n```\n\nUse `option(...)` builder methods, `extra` maps, or `TaskRequest::custom(...)`\nfor operation-specific options that are not yet typed by this SDK.\n\n### Recorded metadata fixtures\n\nNormal CI parses committed fixtures from `tests/fixtures/cloudconvert/` and does\nnot require CloudConvert credentials. Refresh those fixtures only when you want\nto intentionally review upstream metadata drift.\n\n```sh\nexport CLOUDCONVERT_API_KEY=\u003cAPI_KEY\u003e\n\ncurl --get \"https://api.cloudconvert.com/v2/operations\" \\\n  --header \"Authorization: Bearer ${CLOUDCONVERT_API_KEY}\" \\\n  --data-urlencode \"filter[operation]=convert\" \\\n  --data-urlencode \"filter[input_format]=docx\" \\\n  --data-urlencode \"filter[output_format]=pdf\" \\\n  --data-urlencode \"include=options,engine_versions\" \\\n  --data-urlencode \"alternatives=true\" \\\n  --output tests/fixtures/cloudconvert/operations-convert-docx-pdf.json\n\ncurl --get \"https://api.cloudconvert.com/v2/operations\" \\\n  --header \"Authorization: Bearer ${CLOUDCONVERT_API_KEY}\" \\\n  --data-urlencode \"filter[operation]=metadata\" \\\n  --data-urlencode \"filter[input_format]=pdf\" \\\n  --data-urlencode \"include=options,engine_versions\" \\\n  --output tests/fixtures/cloudconvert/operations-metadata.json\n\ncurl --get \"https://api.cloudconvert.com/v2/operations\" \\\n  --header \"Authorization: Bearer ${CLOUDCONVERT_API_KEY}\" \\\n  --data-urlencode \"filter[operation]=metadata/write\" \\\n  --data-urlencode \"filter[input_format]=pdf\" \\\n  --data-urlencode \"include=options,engine_versions\" \\\n  --output tests/fixtures/cloudconvert/operations-metadata-write.json\n```\n\nReview fixture diffs before committing them. If the response shape changed,\nupdate `tests/metadata_contract.rs` with an assertion for the new contract\ninstead of weakening existing checks.\n\n## Retry\n\nAutomatic retry is off by default. Enable the optional feature and set a policy:\n\n```toml\ncloudconvert-sdk = { version = \"0.3\", features = [\"retry\"] }\n```\n\n```rust\nuse std::time::Duration;\n\nuse cloudconvert_sdk::{ApiKey, CloudConvertClient, RetryPolicy, TransportConfig};\n\nlet client = CloudConvertClient::builder(ApiKey::from_env()?)\n    .transport_config(\n        TransportConfig::default()\n            .connect_timeout(Duration::from_secs(10))\n            .request_timeout(Duration::from_secs(120)),\n    )\n    .retry_policy(\n        RetryPolicy::new(3)\n            .initial_delay(Duration::from_millis(250))\n            .max_delay(Duration::from_secs(10)),\n    )\n    .build()?;\n```\n\nRetry applies only to idempotent CloudConvert API requests. It retries transient\nstatuses `429`, `500`, `502`, `503`, and `504`, plus connect and timeout errors.\nIt does not retry job or task creation, signed `import/upload` form\nsubmissions, or `export/url` downloads.\n\n## Socket.IO waits\n\nEnable the optional feature when an async application wants lower-latency\ncompletion than polling and does not expose a public webhook receiver:\n\n```toml\ncloudconvert-sdk = { version = \"0.3\", features = [\"socket\"] }\n```\n\n```rust\nuse cloudconvert_sdk::{ApiKey, CloudConvertClient, FileExtension, JobCreateRequest};\n\nasync fn run() -\u003e cloudconvert_sdk::Result\u003c()\u003e {\n    let client = CloudConvertClient::builder(ApiKey::from_env()?).build()?;\n    let request = JobCreateRequest::linear()\n        .import_url(\"https://example.test/input.docx\")\n        .convert(FileExtension::Pdf)?\n        .export_url()?\n        .build();\n\n    let finished = client.jobs().create_and_wait_socket(request).await?;\n    for file in finished.export_urls() {\n        println!(\"{}\", file.filename);\n    }\n\n    Ok(())\n}\n```\n\nThe managed wait helpers subscribe, check the current resource state to avoid\nmissing fast completions, wait for a terminal Socket.IO event, and disconnect.\nUse webhooks when CloudConvert can call your service directly.\n\nFor streams, use `client.socket(...)` with `SocketChannel`,\n`jobs().task_events_socket(job_id)`, or `users().events_socket()`.\n\n## Runnable examples\n\nThese examples build request payloads and print JSON. They do not call the live\nCloudConvert API, so they are safe to run without credentials:\n\n```sh\ncargo run --example build_job\ncargo run --example linear_options_job\ncargo run --example branch_job\ncargo run --example advanced_job\ncargo run --example file_extensions\n```\n\n## Build and test\n\nRun the local validation loop before opening a pull request:\n\n```sh\ncargo fmt --all -- --check\ncargo check --workspace --all-targets --locked\ncargo check --workspace --all-targets --all-features --locked\ncargo clippy --workspace --all-targets --all-features --locked -- -D warnings\ncargo test --workspace --all-targets --locked\ncargo test --workspace --all-targets --all-features --locked\ncargo test --doc --all-features --locked\n```\n\nCI also generates an `llvm-cov` HTML coverage artifact and enforces the current\nline coverage threshold.\n\n## Live API tests\n\nLive CloudConvert tests are ignored by default so normal CI and `cargo test` do\nnot consume API credits.\n\nPut a real key in `.env` or the process environment:\n\n```sh\nCLOUDCONVERT_API_KEY=\u003cAPI_KEY\u003e\n```\n\nRun the live group explicitly:\n\n```sh\ncargo test --test live_api -- --ignored\n```\n\nThe live group keeps API usage small. It creates and deletes live tasks and\njobs, including a watermark job shape, and has one ignored\nupload-convert-export flow with a tiny generated text file. It needs task and\njob scopes, not `user.read`.\n\n## Source map\n\n- Public exports and crate-level examples: [`src/lib.rs`](src/lib.rs)\n- Client configuration and optional retry policy: [`src/config.rs`](src/config.rs)\n- Resource clients, upload helpers, and download helpers: [`src/client.rs`](src/client.rs)\n- Job builders and response models: [`src/jobs.rs`](src/jobs.rs)\n- Task payload builders: [`src/tasks.rs`](src/tasks.rs)\n- Operation metadata validation: [`src/operations.rs`](src/operations.rs)\n- OAuth 2.0 flows: [`src/oauth.rs`](src/oauth.rs)\n- Webhook and signed URL helpers: [`src/webhook.rs`](src/webhook.rs),\n  [`src/signed_url.rs`](src/signed_url.rs)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbnomei%2Fcloudconvert-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbnomei%2Fcloudconvert-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbnomei%2Fcloudconvert-sdk/lists"}