Python SDK for the LinWheel content engine API
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file linwheel-0.1.0.tar.gz.
File metadata
- Download URL: linwheel-0.1.0.tar.gz
- Upload date:
- Size: 29.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
831cdb0d54bb23e19d21544e347fb5dba9b0c2ae0c3f538a2fc9be9041ec9125
|
|
| MD5 |
4af631fad65aae34c8b8a406c0410126
|
|
| BLAKE2b-256 |
b4f6d1f68ac630dd99526b454c13aa3fcd137dca9a436cb433ed0238d2f1a5fa
|
Provenance
The following attestation bundles were made for linwheel-0.1.0.tar.gz:
Publisher:
publish-pypi.yml on Peleke/linwheel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
linwheel-0.1.0.tar.gz -
Subject digest:
831cdb0d54bb23e19d21544e347fb5dba9b0c2ae0c3f538a2fc9be9041ec9125 - Sigstore transparency entry: 984173801
- Sigstore integration time:
-
Permalink:
Peleke/linwheel@ac36002a5013a048a91babb5e1cd8c5c3f917fda -
Branch / Tag:
refs/tags/py-v0.1.0 - Owner: https://github.com/Peleke
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ac36002a5013a048a91babb5e1cd8c5c3f917fda -
Trigger Event:
push
-
Statement type:
File details
Details for the file linwheel-0.1.0-py3-none-any.whl.
File metadata
- Download URL: linwheel-0.1.0-py3-none-any.whl
- Upload date:
- Size: 7.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d68516ec10fd8def93fcd608da7a714f51558a0fabbb8448fd358a2b0d10fe3d
|
|
| MD5 |
7b7aa8563b20ec1e90f03d1359f56487
|
|
| BLAKE2b-256 |
2208dde1972aa356e88de033efc075ed0e0ddb8d30c2c08c93e42ebf68b62d2a
|
Provenance
The following attestation bundles were made for linwheel-0.1.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on Peleke/linwheel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
linwheel-0.1.0-py3-none-any.whl -
Subject digest:
d68516ec10fd8def93fcd608da7a714f51558a0fabbb8448fd358a2b0d10fe3d - Sigstore transparency entry: 984173804
- Sigstore integration time:
-
Permalink:
Peleke/linwheel@ac36002a5013a048a91babb5e1cd8c5c3f917fda -
Branch / Tag:
refs/tags/py-v0.1.0 - Owner: https://github.com/Peleke
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ac36002a5013a048a91babb5e1cd8c5c3f917fda -
Trigger Event:
push
-
Statement type: