linwheel
Python SDK for the LinWheel content engine API. Analyze, reshape, refine, and schedule LinkedIn content programmatically.
Built on httpx + pydantic. Sync client with full type hints.
Install
pip install linwheel
Quick Start
from linwheel import LinWheel
lw = LinWheel(api_key="lw_sk_...")
# Analyze content for LinkedIn potential
analysis = lw.analyze(text="Today I shipped a new SDK...")
# → { "linkedinFit": { "score": 8 }, "suggestedAngles": [...] }
# Reshape into angle-specific posts
result = lw.reshape(
text="Today I shipped a new SDK...",
angles=["field_note", "contrarian"],
save_drafts=True,
)
# Refine a draft
result = lw.refine(text=result["posts"][0]["text"], intensity="medium")
# Approve and schedule
lw.posts.approve(post_id, approved=True)
lw.posts.schedule(post_id, scheduled_at="2026-02-24T09:00:00Z", auto_publish=True)
API Reference
Content Processing
# Analyze text for LinkedIn potential — topics, angles, fit score
analysis = lw.analyze(text="...", context="buildlog")
# Reshape content through 7 angle lenses
result = lw.reshape(
text="...",
angles=["field_note", "contrarian", "demystification"],
pre_edit=False, # light-edit input first
instructions="...", # custom instructions
save_drafts=True, # persist as drafts
)
# Refine with parameterized intensity
result = lw.refine(
text="...",
intensity="light", # "light" | "medium" | "heavy"
instructions="...",
post_type="field_note",
save_draft=True,
)
# Split long content into a post series
result = lw.split(
text="...",
max_posts=4, # 2-10, default 5
instructions="...",
save_drafts=True,
)
Drafting
# Create a manual draft
draft = lw.draft(
full_text="Your post content...",
hook="Opening line",
post_type="field_note",
approved=False,
auto_publish=True,
scheduled_at="2026-02-24T09:00:00Z",
)
# Create a draft with image + carousel in one call
bundle = lw.bundle(
full_text="Your post content...",
image_headline_text="Big Title",
image_style_preset="dark_mode",
carousel_slides=[{"headlineText": "Slide 1"}, {"headlineText": "Slide 2"}],
)
Post Management
# List posts with filters
result = lw.posts.list(approved=False, limit=10)
# Get, update, approve, schedule
post = lw.posts.get(post_id)
lw.posts.update(post_id, full_text="Updated content...")
lw.posts.approve(post_id, approved=True)
lw.posts.schedule(post_id, scheduled_at="2026-02-24T09:00:00Z")
# Generate visuals
lw.posts.image(post_id, headline_text="...", style_preset="dark_mode")
lw.posts.carousel(
post_id,
slides=[{"headlineText": "Title"}, {"headlineText": "Content"}],
style_preset="accent_bar",
)
Voice Profiles
Voice profiles inject your writing style into all content generation. Provide writing samples and LinWheel matches your voice.
# Create a voice profile from your writing
result = lw.voice_profiles.create(
name="My Writing Voice",
description="Technical, direct, slightly irreverent",
samples=[article1, article2, article3],
is_active=True,
)
# List all profiles
result = lw.voice_profiles.list()
# → { "profiles": [...], "activeProfileId": "..." }
# Switch active profile
lw.voice_profiles.activate(profile_id)
# Delete a profile
lw.voice_profiles.delete(profile_id)
Configuration
lw = LinWheel(
api_key="lw_sk_...", # required
base_url="https://www.linwheel.io", # default
signing_secret="your-hmac-secret", # optional, for request signing
timeout=60.0, # default: 60s
)
# Context manager support
with LinWheel(api_key="lw_sk_...") as lw:
lw.analyze(text="...")
HMAC Request Signing
If your API key has a signing secret, the SDK automatically signs every request with X-LW-Signature:
X-LW-Signature: t=<unix-seconds>,v1=<hmac-sha256-hex>
Canonical payload: <timestamp>.<METHOD>.<path>.<body-sha256>
Error Handling
from linwheel import LinWheel, LinWheelApiError, LinWheelAuthError
lw = LinWheel(api_key="lw_sk_...")
try:
lw.analyze(text="...")
except LinWheelAuthError as e:
# 401 — invalid or missing API key
print(e, e.response_body)
except LinWheelApiError as e:
# 4xx/5xx
print(e.status_code, e, e.response_body)
The 7 Content Angles
| Angle | What it does |
|---|---|
contrarian |
Challenge conventional wisdom |
field_note |
Share a specific observation from experience |
demystification |
Break down something complex |
identity_validation |
Make the reader feel seen |
provocateur |
Be deliberately provocative |
synthesizer |
Connect dots across domains |
curious_cat |
Ask genuine questions |
Agent Example
The end-to-end flow from raw content to a scheduled LinkedIn post:
from linwheel import LinWheel
lw = LinWheel(api_key="lw_sk_...")
# 1. Feed in today's work
analysis = lw.analyze(text=daily_notes, context="buildlog")
# 2. Reshape through the best angles
top_angles = [a["angle"] for a in analysis["suggestedAngles"][:3]]
result = lw.reshape(text=daily_notes, angles=top_angles, save_drafts=True)
# 3. Refine the best draft
best = result["posts"][0]
refined = lw.refine(text=best["text"], intensity="medium", save_draft=True)
# 4. Approve and schedule for tomorrow 9am
lw.posts.approve(refined["postId"], approved=True)
lw.posts.schedule(refined["postId"], scheduled_at="2026-02-24T09:00:00Z", auto_publish=True)
License
MIT
Metadata
Release files for linwheel 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| linwheel-0.1.0.tar.gz | 29.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| linwheel-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 37.3 kB
Release files / linwheel-0.1.0.tar.gz
| Download URL | linwheel-0.1.0.tar.gz |
|---|---|
| Size | 29.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
831cdb0d54bb23e19d21544e347fb5dba9b0c2ae0c3f538a2fc9be9041ec9125
|
|
BLAKE2b-256 checksum How to use checksums |
b4f6d1f68ac630dd99526b454c13aa3fcd137dca9a436cb433ed0238d2f1a5fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 24, 2026.
Transparency logRelease files / linwheel-0.1.0-py3-none-any.whl
| Download URL | linwheel-0.1.0-py3-none-any.whl |
|---|---|
| Size | 7.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d68516ec10fd8def93fcd608da7a714f51558a0fabbb8448fd358a2b0d10fe3d
|
|
BLAKE2b-256 checksum How to use checksums |
2208dde1972aa356e88de033efc075ed0e0ddb8d30c2c08c93e42ebf68b62d2a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 24, 2026.
Transparency log