Skip to main content

Makra Python SDK

The official Python client for the Makra web extraction API.

Install

pip install makra

For local development from this repository:

pip install -e sdk/python

Quick start

from makra import Makra

with Makra(api_key="mk_live_...") as client:
    result = client.extract(
        urls=["https://example.com"],
        schema={"title": "The page title"},
    )
    print(result)

Every operation exists in two flavours: Makra (synchronous) and AsyncMakra (awaitable). The examples below use the synchronous client; the async one is identical with await and async with.

Three ways to run a workflow

Pick by how long the work takes and how much you want to watch it happen.

Mode Use when Method
REST The run is short and you just want the answer extract / schema
Streaming You want live progress, e.g. to drive a UI extract_stream / schema_stream
Deferred The run is long, or the caller cannot stay online submit_extract / submit_schema

1. REST

The connection is held until the run finishes, so timeout is really "how long may this workflow take". It defaults to 300 seconds per origin page. Paginated extracts (pagination.additional_pages) and sequential multi-URL extracts scale that budget automatically unless you pass timeout yourself.

from makra import (
    EventTypes,
    ExtractOptions,
    Iso3166Alpha2,
    Makra,
    ProxyContinents,
    ProxyRegion,
    ProxyRegionScopes,
    StreamDetailTypes,
    ValidationModes,
)

data = client.extract(
    urls=["https://example.com/products"],
    schema={"products": [{"name": "string", "price": "number"}]},
    config=ExtractOptions(
        validation_mode=ValidationModes.REPAIR,
        additional_pages=2,
        proxy_region=ProxyRegion.country(Iso3166Alpha2.DE),
        recovery_retry=True,
        recovery_retry_delay_ms=2000,
    ),
    timeout=120,
)

Nested dictionaries remain supported and produce the same wire payload:

data = client.extract(
    urls=["https://example.com/products"],
    schema={"products": [{"name": "string", "price": "number"}]},
    config={
        "validation_mode": ValidationModes.REPAIR,
        "pagination": {"enabled": True, "additional_pages": 2},
        "crawler": {
            "proxy": {
                "region": {
                    "scope": ProxyRegionScopes.COUNTRY,
                    "value": Iso3166Alpha2.DE,
                }
            },
            "recovery": {"retry": True, "retry_delay_ms": 2000},
        },
    },
)

Discover what a page contains before you write a schema:

page_schema = client.schema(
    "https://example.com/products",
    config={
        "crawler": {
            "proxy": {
                "region": {
                    "scope": ProxyRegionScopes.CONTINENT,
                    "value": ProxyContinents.EUROPE,
                }
            }
        }
    },
)

2. Streaming

The stream carries lifecycle and progress events, not the extracted data. When a terminal event arrives, fetch the stored result.

run_id = None
for event in client.extract_stream(urls=urls, schema=schema):
    run_id = event.run_id
    print(event.sequence, event.type, event.detail_type)
    if event.type == EventTypes.STEP_PROGRESS:
        if event.detail_type == StreamDetailTypes.RUN_TITLE_GENERATED:
            print("title:", event.payload.get("title"))
    if event.is_terminal:
        print("finished:", event.status, event.reason)

data = client.get_run_result(run_id)

Each WorkflowEvent exposes type, sequence, run_id, the raw payload, and the shortcuts is_terminal, detail_type, status, reason, success. Compare event.type to EventTypes and event.detail_type to StreamDetailTypes.

If the connection drops mid-run, the SDK reconnects to the run's event endpoint with Last-Event-ID, so you see every event exactly once. Silence longer than stream_idle_timeout (90s, versus the gateway's 15s heartbeat) counts as a dropped connection.

3. Deferred runs

Submit now, collect later — from a different process, if you like.

run = client.submit_extract(urls=urls, schema=schema)
print(run.id, run.state)          # queued

run.wait()                        # polls until terminal
data = run.result()

Use run_is_terminal and run_succeeded when you have a run mapping and need to distinguish infrastructure completion from domain success. A completed run with no success field is treated as successful; success: false is not.

from makra import run_is_terminal, run_succeeded

if run_is_terminal(snapshot) and run_succeeded(snapshot):
    data = client.get_run_result(snapshot["id"])

A handle also supports refresh(), stream(), and cancel(). Everything a handle does is available directly on the client too — get_run, list_runs, wait_for_run, stream_run_events, get_run_result, cancel_run — so a run id is all you need to resume from anywhere.

for event in client.stream_run_events(run_id, last_event_id=42):
    ...

Configuration

Settings resolve as explicit argument → environment variable → default.

Argument Environment Default
api_key MAKRA_API_KEY makra-development-key
base_url MAKRA_BASE_URL https://api.makralabs.org
timeout MAKRA_TIMEOUT (seconds) 300 per origin page
max_retries MAKRA_MAX_RETRIES 2
connect_timeout 10
stream_idle_timeout 90
retry_backoff 0.5
default_headers {}

default_headers may add tracing or application headers. It cannot override SDK-owned names (Api-Key, Content-Type, Accept, User-Agent, Idempotency-Key, Prefer, Last-Event-ID); pass api_key, idempotency_key, or last_event_id instead.

from makra import DEVELOPMENT_BASE_URL, Makra

client = Makra(base_url=DEVELOPMENT_BASE_URL, timeout=60, max_retries=4)

Authentication uses the Api-Key header. A client owns an HTTP connection pool, so build one per application and close it when done.

Retries and idempotency

Transient failures (408, 409, 425, 429, 5xx) are retried with exponential backoff and full jitter, honouring Retry-After when the server sends it.

Retrying a submission is only safe because the SDK attaches a fresh Idempotency-Key to every one: the gateway replays the original run instead of starting a second billable one. Pass your own idempotency_key to make a submission replayable across process restarts too.

Errors

Catch MakraError for anything the SDK raises, or a subclass to handle one condition.

MakraError
├── MakraAPIError                     .status_code .code .body .request_id
│   ├── MakraAuthenticationError      401
│   ├── MakraInsufficientCreditsError 402  .required_credits .available_credits
│   ├── MakraPermissionError          403
│   ├── MakraNotFoundError            404
│   ├── MakraInvalidRequestError      4xx  .field .reason .index
│   ├── MakraRateLimitError           429  .retry_after .concurrency
│   └── MakraServerError              5xx
├── MakraConnectionError              the request never reached the API
│   └── MakraTimeoutError
├── MakraStreamError                  .run_id
│   └── MakraResultError              .run_id .location   (also a MakraStreamError)
└── MakraRunFailedError               .run_id .state .run

MakraResultError is raised when a stored-result redirect is missing Location or the location is not an absolute HTTP(S) URL. It subclasses MakraStreamError so existing stream catches keep working; a future major version may reparent it under MakraError. Presigned query strings are never included in the message or location.

Malformed arguments raise ValueError before any network call, so a typo in a config key costs nothing.

from makra import MakraInsufficientCreditsError, MakraRateLimitError

try:
    client.extract(urls=urls, schema=schema)
except MakraInsufficientCreditsError as error:
    print("need", error.required_credits, "have", error.available_credits)
except MakraRateLimitError as error:
    print("retry after", error.retry_after)

Async

import asyncio
from makra import AsyncMakra

async def main():
    async with AsyncMakra() as client:
        async for event in client.extract_stream(urls=urls, schema=schema):
            print(event.type)
        run = await client.submit_schema("https://example.com")
        await run.wait()
        print(await run.result())

asyncio.run(main())

See ../SPEC.md for the complete contract.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

makra-0.0.3.tar.gz (49.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

makra-0.0.3-py3-none-any.whl (35.9 kB view details)

Uploaded Python 3

File details

Details for the file makra-0.0.3.tar.gz.

File metadata

  • Download URL: makra-0.0.3.tar.gz
  • Upload date:
  • Size: 49.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.0

File hashes

Hashes for makra-0.0.3.tar.gz
Algorithm Hash digest
SHA256 98180f2268fd81c0680c73770dbb78d3f2c0e4e4d51e34858aa0e62b741161e1
MD5 9d68e25ca62a78f8510a34177acb70cf
BLAKE2b-256 898d9db624d8c0c06266a323b8c44ac50819be1e165a718ec7d5e02c0674674b

See more details on using hashes here.

File details

Details for the file makra-0.0.3-py3-none-any.whl.

File metadata

  • Download URL: makra-0.0.3-py3-none-any.whl
  • Upload date:
  • Size: 35.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.0

File hashes

Hashes for makra-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 2c245ea26c7481c355cd0c03ae864d0abad2d84cae18477aa9ad341160a55ba3
MD5 ec4e58e15e2537317652d9c3cde8006a
BLAKE2b-256 cf908e560fbaec5f18a50c0b1c25541a66c84f7f7f6d3d8f9ec95e1b1774a465

See more details on using hashes here.

Release history Release notifications | RSS feed

0.0.4

2 files

This release

0.0.3 This release

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page