Last active
November 19, 2025 13:50
-
-
Save nsantorello/9a54fe603fc07e54d72ff668a089296f to your computer and use it in GitHub Desktop.
Printago: One-off printing of an STL via API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "parts": [ | |
| { | |
| "partId": "YOUR_PART_ID", | |
| "quantity": 1, | |
| "tags": {}, | |
| "label": "PRINT_JOB_NAME", | |
| "parameterOverrides": [] | |
| } | |
| ] | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "parts": [ | |
| { | |
| "partId": "YOUR_PART_ID", | |
| "quantity": 1, | |
| "tags": { | |
| "printer.id": "=YOUR_PRINTER_ID" | |
| }, | |
| "label": "PRINT_JOB_NAME", | |
| "parameterOverrides": [] | |
| } | |
| ] | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import List, Optional, Dict, Any | |
| import hashlib | |
| import logging | |
| from urllib.parse import urlparse, unquote | |
| import requests | |
| from requests.exceptions import RequestException | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| @dataclass | |
| class Config: | |
| api_key: str | |
| store_id: str | |
| base_url: str = "https://api.printago.io" | |
| model_file_path: Path = Path("path/to/your/model.stl") | |
| class PrintagoAPIClient: | |
| def __init__(self, config: Config): | |
| self.config = config | |
| self.headers = { | |
| "Content-Type": "application/json", | |
| "Authorization": f"ApiKey {self.config.api_key}", | |
| "x-printago-storeid": self.config.store_id | |
| } | |
| @staticmethod | |
| def calculate_file_hash(file_path: Path) -> str: | |
| """Calculate SHA256 hash of a file.""" | |
| sha256_hash = hashlib.sha256() | |
| try: | |
| with file_path.open("rb") as f: | |
| for byte_block in iter(lambda: f.read(4096), b""): | |
| sha256_hash.update(byte_block) | |
| return sha256_hash.hexdigest() | |
| except IOError as e: | |
| logger.error(f"Failed to read file for hashing: {e}") | |
| raise | |
| def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]: | |
| """Make an HTTP request to the API.""" | |
| url = f"{self.config.base_url}/v1/{endpoint}" | |
| try: | |
| response = requests.request(method, url, headers=self.headers, **kwargs) | |
| response.raise_for_status() | |
| return response.json() | |
| except RequestException as e: | |
| logger.error(f"API request failed: {e}") | |
| raise | |
| def get_signed_url(self) -> Dict[str, Any]: | |
| """Get a signed URL for file upload.""" | |
| payload = {"filenames": [self.config.model_file_path.name]} | |
| return self._make_request("POST", "storage/signed-upload-urls", json=payload) | |
| def upload_file(self, upload_url: str) -> bool: | |
| """Upload a file to the provided URL.""" | |
| try: | |
| with self.config.model_file_path.open('rb') as file: | |
| response = requests.put(upload_url, data=file) | |
| response.raise_for_status() | |
| return True | |
| except (IOError, RequestException) as e: | |
| logger.error(f"File upload failed: {e}") | |
| return False | |
| def create_part(self, file_uri: str) -> Dict[str, Any]: | |
| """Create a new part in the system.""" | |
| payload = { | |
| "name": self.config.model_file_path.name, | |
| "description": "", | |
| "fileUris": [file_uri], | |
| "fileHashes": [self.calculate_file_hash(self.config.model_file_path)], | |
| "parameters": [], | |
| "printTags": {}, | |
| "allowedFilamentTypes": [], | |
| "type": "stl", | |
| "overriddenProcessProfileId": None, | |
| "storeId": self.config.store_id | |
| } | |
| return self._make_request("POST", "parts", json=payload) | |
| def create_build(self, part_id: str) -> Dict[str, Any]: | |
| """Create a new build using the specified part.""" | |
| payload = { | |
| "parts": [{ | |
| "partId": part_id, | |
| "quantity": 1, | |
| "tags": {}, | |
| "label": self.config.model_file_path.name, | |
| "parameterOverrides": [] | |
| }] | |
| } | |
| return self._make_request("POST", "builds", json=payload) | |
| def main(): | |
| config = Config( | |
| api_key="your-api-key", | |
| store_id="your-store-id", | |
| model_file_path=Path("model.stl") | |
| ) | |
| client = PrintagoAPIClient(config) | |
| try: | |
| # Step 1: Get signed URL | |
| signed_url_response = client.get_signed_url() | |
| upload_url = signed_url_response["signedUrls"][0]["uploadUrl"] | |
| file_uri = signed_url_response["signedUrls"][0]["path"] | |
| # Step 2: Upload file | |
| if not client.upload_file(upload_url): | |
| logger.error("File upload failed") | |
| return | |
| logger.info("File uploaded successfully") | |
| # Step 3: Create part | |
| part_response = client.create_part(file_uri) | |
| part_id = part_response["id"] | |
| logger.info(f"Part created with ID: {part_id}") | |
| # Step 4: Create build | |
| build_response = client.create_build(part_id) | |
| logger.info(f"Build created: {build_response}") | |
| except Exception as e: | |
| logger.error(f"An error occurred: {e}") | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| requests==2.31.0 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The JSON files represent 3 requests:
To get these variables:
YOUR_PART_ID- click on your part in the Parts page and look at the url, it should end in/parts/YOUR_PART_ID... e.g. if the URL ishttps://app.printago.io/parts/c8x2dorgp40blck610w4bgg4the part ID isc8x2dorgp40blck610w4bgg4YOUR_PRINTER_ID- click on your printer in the Printers page and look at the url, it should end in/printers/YOUR_PRINTER_IDTAG1andTAG2- these are tags (case sensitive) the printer needs to have in order to match a print job to it. They can be anything you want. The tags are pipe-delimited -- this character:|PRINT_JOB_NAME- this is just whatever you want to see it show up as in the print queue, e.g. an order number