Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mavera.io/llms.txt

Use this file to discover all available pages before exploring further.

Scenario

When features move to “Done” in Linear, your marketing team needs release notes, a blog post draft, and social media copy. This job queries completed issues from recent cycles via GraphQL, extracts feature names and descriptions, then chains three Mavera Generate calls for release notes, a blog draft, and social copy. Flow: Linear GraphQL (completedIssues query) → extract features → Mavera POST /api/v1/generations (release notes) → POST /api/v1/generations (blog) → POST /api/v1/generations (social)

Code

import os, requests, time

LN, MV = os.environ["LINEAR_API_KEY"], os.environ["MAVERA_API_KEY"]
LB, MB = "https://api.linear.app/graphql", "https://app.mavera.io/api/v1"
LH = {"Authorization": LN, "Content-Type": "application/json"}
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

query = """query($tk: String!) {
  issues(filter: { team: { key: { eq: $tk } }, state: { type: { eq: "completed" } },
    completedAt: { gte: "-P14D" } }, first: 50, orderBy: completedAt) {
    nodes { identifier title description labels { nodes { name } } project { name } estimate }
  }
}"""

r = requests.post(LB, headers=LH, json={"query": query, "variables": {"tk": "ENG"}})
if r.status_code == 429:
    time.sleep(int(r.headers.get("Retry-After", 60)))
    r = requests.post(LB, headers=LH, json={"query": query, "variables": {"tk": "ENG"}})
r.raise_for_status()
issues = r.json()["data"]["issues"]["nodes"]
print(f"Fetched {len(issues)} completed issues")

features = "\n\n".join(
    f"- [{i['identifier']}] {i['title']}\n  Project: {(i.get('project') or {}).get('name','None')}\n"
    f"  Labels: {', '.join(l['name'] for l in i.get('labels',{}).get('nodes',[])) or 'none'}\n"
    f"  Description: {(i.get('description') or '')[:300]}"
    for i in issues)

time.sleep(0.3)
notes = requests.post(f"{MB}/generations", headers=MH, json={
    "prompt": f"Technical writer. Release notes for {len(issues)} features.\n\n{features[:4000]}\n\n"
              "Group by project. 1-2 sentences each. User benefit first. Include identifier.",
}).json()
release_notes = notes.get("output", notes.get("content", ""))

time.sleep(0.3)
blog = requests.post(f"{MB}/generations", headers=MH, json={
    "prompt": f"Blog post announcing updates.\n\n{release_notes[:3000]}\n\n"
              "Headline, intro, 3-4 highlights, what's next, CTA. 400-600 words.",
}).json()

time.sleep(0.3)
social = requests.post(f"{MB}/generations", headers=MH, json={
    "prompt": f"Social copy for release.\n\n{release_notes[:2000]}\n\n"
              "- Twitter/X (280 chars)\n- LinkedIn (150 words)\n- Product Hunt (3 sentences)\nMax 2 hashtags.",
}).json()

print(f"\nRELEASE NOTES:\n{release_notes[:1500]}")
print(f"\nBLOG:\n{blog.get('output', blog.get('content',''))[:1500]}")
print(f"\nSOCIAL:\n{social.get('output', social.get('content',''))[:1500]}")

Example Output

Fetched 23 completed issues

RELEASE NOTES:
## Dashboard & Analytics
- Real-time webhook delivery dashboard with retry visibility (ENG-412)
  Monitor webhook health at a glance — delivery rates, failures, retries.
- Custom date range picker for all analytics views (ENG-398)

## API Platform
- GraphQL subscriptions for issue updates (ENG-407)
- Batch mutation support for bulk issue updates (ENG-391)

SOCIAL:
Twitter/X: We shipped 23 features in 2 weeks. Real-time webhooks,
batch mutations, Figma previews — all live now. #devtools

LinkedIn: Our engineering team closed 23 issues across 3 projects.
Highlights: real-time webhook monitoring, GraphQL subscriptions...

Error Handling

Linear allows 5,000 req/hr and 250K complexity points/hr. The code reads Retry-After on 429 responses. Nested queries with large first: values consume more points.
The gte: "-P14D" ISO 8601 duration filters to the last 14 days. If no issues match, extend the window or verify your team’s workflow states include a “completed” type.
Issues without descriptions produce thinner release notes. Filter to description length > 50 chars for higher quality, or enrich from linked PRs via the attachments connection.