sample API scripts
chevron_leftchevron_right
"""
Bas-Relief Public API (Python) — production sample
Recommended: provide your API key via environment variable.
Windows (PowerShell):
$env:BASRELIEF_API_KEY = "YOUR_API_KEY"
macOS/Linux (bash/zsh):
export BASRELIEF_API_KEY="YOUR_API_KEY"
"""
import base64
import json
import os
import sys
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List, Optional
from urllib.error import HTTPError, URLError
from urllib.parse import urljoin, urlparse
from urllib.request import Request, urlopen
DEFAULT_BASE_URL = "https://bas-relief.ai"
BASE_URL: str = DEFAULT_BASE_URL
APPROACH: str = "A"
# =====================
# EDIT THESE SETTINGS
# =====================
# Recommended: set env var BASRELIEF_API_KEY instead of committing a key into code.
API_KEY: str = os.getenv("BASRELIEF_API_KEY", "").strip()
# Job inputs
SUBJECT: str = "YOUR_SUBJECT" # e.g. cats, flowers, predators, plane-tree
TEXT_TOP: str = ""
TEXT_CENTER: str = "YOUR_TEXT"
TEXT_BOTTOM: str = ""
TEXT_BOTTOM2: str = ""
LOCALE: str = "en" # If you enter subject in another language, change locale accordingly.
METHOD_NO: str = "C"
# Style: choose ONE of these options
STYLE_NO: int = 41 # e.g. 1, 2, 3, ...
CUSTOM_STYLE_TEXT: Optional[str] = None # e.g. "baroque, ornate"
# Size (optional)
WIDTH: Optional[int] = 1024
HEIGHT: Optional[int] = 1024
# Optional: mask image path (png/jpg/webp). Set to None to not upload a mask.
# Examples:
# MASK_PATH = str(Path.home() / "mask.png")
# MASK_PATH = "/absolute/path/to/mask.png" # macOS/Linux
MASK_PATH: Optional[str] = None
# Optional: client reference / correlation id
CLIENT_REFERENCE: Optional[str] = None
# Download folder
OUTDIR: str = str(Path.cwd() / "downloads")
# Polling behavior
TIMEOUT_SECONDS: int = 15 * 60
POLL_SECONDS: float = 2.0
@dataclass(frozen=True)
class ApiConfig:
base_url: str
api_key: str
timeout_seconds: int
poll_seconds: float
class ApiError(RuntimeError):
pass
def _json_request(
method: str,
url: str,
*,
headers: Dict[str, str],
body: Optional[Dict[str, Any]] = None,
timeout: int = 60,
) -> Any:
data = None
req_headers = {"Accept": "application/json", **headers}
if body is not None:
data = json.dumps(body).encode("utf-8")
req_headers["Content-Type"] = "application/json"
req = Request(url, method=method, headers=req_headers, data=data)
try:
with urlopen(req, timeout=timeout) as resp:
raw = resp.read()
if not raw:
return None
return json.loads(raw.decode("utf-8"))
except HTTPError as e:
raw = None
try:
raw = e.read()
except Exception:
pass
detail = None
if raw:
try:
detail = json.loads(raw.decode("utf-8"))
except Exception:
detail = raw.decode("utf-8", errors="replace")
raise ApiError(f"HTTP {e.code} calling {url}: {detail}") from e
except URLError as e:
raise ApiError(f"Network error calling {url}: {e}") from e
def _download_file(
url: str,
dest_path: Path,
*,
headers: Dict[str, str],
timeout: int = 120,
) -> None:
dest_path.parent.mkdir(parents=True, exist_ok=True)
req = Request(url, method="GET", headers=headers)
try:
with urlopen(req, timeout=timeout) as resp:
dest_path.write_bytes(resp.read())
except HTTPError as e:
raise ApiError(f"HTTP {e.code} downloading {url}") from e
except URLError as e:
raise ApiError(f"Network error downloading {url}: {e}") from e
def _normalize_base_url(base_url: str) -> str:
base_url = (base_url or "").strip()
if not base_url:
return DEFAULT_BASE_URL
return base_url.rstrip("/")
def _artifact_filename(artifact: Dict[str, Any]) -> str:
fn = (artifact.get("filename") or "").strip()
if fn:
return fn
url = (artifact.get("url") or "").strip()
if url:
p = urlparse(url)
name = Path(p.path).name
if name:
return name
return "artifact.bin"
def _read_mask_as_base64(mask_path: Path) -> Dict[str, str]:
if not mask_path.exists():
raise ApiError(f"Mask file not found: {mask_path}")
ext = mask_path.suffix.lower().lstrip(".")
if ext == "jpeg":
ext = "jpg"
if ext not in {"png", "jpg", "webp"}:
raise ApiError(f"Unsupported mask extension: .{ext} (use png/jpg/webp)")
raw = mask_path.read_bytes()
return {
"mask_image_base64": base64.b64encode(raw).decode("ascii"),
"mask_image_ext": ext,
}
def create_job(cfg: ApiConfig, payload: Dict[str, Any]) -> Dict[str, Any]:
url = f"{cfg.base_url}/v1/jobs"
headers = {"X-API-Key": cfg.api_key}
return _json_request("POST", url, headers=headers, body=payload, timeout=60)
def get_job_status(cfg: ApiConfig, job_id: str) -> Dict[str, Any]:
url = f"{cfg.base_url}/v1/jobs/{job_id}"
headers = {"X-API-Key": cfg.api_key}
return _json_request("GET", url, headers=headers, timeout=30)
def list_outputs(cfg: ApiConfig, job_id: str) -> List[Dict[str, Any]]:
url = f"{cfg.base_url}/v1/jobs/{job_id}/outputs"
headers = {"X-API-Key": cfg.api_key}
data = _json_request("GET", url, headers=headers, timeout=60)
if not isinstance(data, list):
raise ApiError(f"Unexpected outputs response type: {type(data)}")
return data
def wait_for_terminal(cfg: ApiConfig, job_id: str) -> Dict[str, Any]:
deadline = time.time() + cfg.timeout_seconds
last_status = None
while time.time() < deadline:
status = get_job_status(cfg, job_id)
s = str(status.get("status") or "").lower()
msg = status.get("message")
progress = status.get("progress")
if s != last_status:
print(f"status={s} message={msg} progress={progress}")
last_status = s
else:
print(".", end="", flush=True)
if s in {"done", "completed", "success"}:
print("")
return status
if s in {"failed", "error", "canceled", "cancelled"}:
print("")
raise ApiError(f"Job ended with status={s} message={msg}")
time.sleep(cfg.poll_seconds)
raise ApiError(f"Timed out waiting for job {job_id} after {cfg.timeout_seconds}s")
def main() -> int:
base_url = _normalize_base_url(BASE_URL)
api_key = (API_KEY or "").strip()
if not api_key:
print(
"Missing API key. Set BASRELIEF_API_KEY env var.",
file=sys.stderr,
)
return 2
cfg = ApiConfig(
base_url=base_url,
api_key=api_key,
timeout_seconds=TIMEOUT_SECONDS,
poll_seconds=POLL_SECONDS,
)
style: Dict[str, Any]
if CUSTOM_STYLE_TEXT is not None:
style = {"custom": True, "style_no": None, "custom_text": CUSTOM_STYLE_TEXT}
else:
style = {"custom": False, "style_no": int(STYLE_NO), "custom_text": None}
payload: Dict[str, Any] = {
"subject": SUBJECT,
"text_top": TEXT_TOP,
"text_center": TEXT_CENTER,
"text_bottom": TEXT_BOTTOM,
"text_bottom2": TEXT_BOTTOM2,
"style": style,
"method_no": METHOD_NO,
"approach": APPROACH,
"locale": LOCALE,
"width": WIDTH,
"height": HEIGHT,
"client_reference": CLIENT_REFERENCE,
}
payload = {k: v for k, v in payload.items() if v is not None}
if MASK_PATH:
payload.update(_read_mask_as_base64(Path(MASK_PATH)))
print(f"POST {cfg.base_url}/v1/jobs")
job = create_job(cfg, payload)
job_id = job.get("job_id")
if not job_id:
raise ApiError(f"Create job response missing job_id: {job}")
print(f"Created job_id={job_id}")
wait_for_terminal(cfg, str(job_id))
outputs = list_outputs(cfg, str(job_id))
outdir = Path(OUTDIR) / str(job_id)
outdir.mkdir(parents=True, exist_ok=True)
headers = {"X-API-Key": cfg.api_key}
print(f"Downloading {len(outputs)} artifact(s) to: {outdir}")
for art in outputs:
url = (art.get("url") or "").strip()
if not url:
continue
if not url.lower().startswith("http"):
url = urljoin(cfg.base_url + "/", url.lstrip("/"))
filename = _artifact_filename(art)
dest = outdir / filename
kind = art.get("kind")
print(f"- {kind}: {filename}")
_download_file(url, dest, headers=headers)
print("Done.")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except ApiError as e:
print(f"ERROR: {e}", file=sys.stderr)
raise SystemExit(1)