Skip to content

Instantly share code, notes, and snippets.

@nsantorello
Last active November 19, 2025 13:50
Show Gist options
  • Select an option

  • Save nsantorello/9a54fe603fc07e54d72ff668a089296f to your computer and use it in GitHub Desktop.

Select an option

Save nsantorello/9a54fe603fc07e54d72ff668a089296f to your computer and use it in GitHub Desktop.
Printago: One-off printing of an STL via API
{
"parts": [
{
"partId": "YOUR_PART_ID",
"quantity": 1,
"tags": {},
"label": "PRINT_JOB_NAME",
"parameterOverrides": []
}
]
}
{
"parts": [
{
"partId": "YOUR_PART_ID",
"quantity": 1,
"tags": {
"printer.id": "=YOUR_PRINTER_ID"
},
"label": "PRINT_JOB_NAME",
"parameterOverrides": []
}
]
}
{
"parts": [
{
"partId": "YOUR_PART_ID",
"quantity": 1,
"tags": {
"user.tags": "&=TAG1|TAG2"
},
"label": "PRINT_JOB_NAME",
"parameterOverrides": []
}
]
}
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()
requests==2.31.0
@nsantorello
Copy link
Author

nsantorello commented Oct 17, 2024

The JSON files represent 3 requests:

  1. Just prints the part
  2. Prints to a specific printer
  3. Prints to printers with certain label(s)

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 is https://app.printago.io/parts/c8x2dorgp40blck610w4bgg4 the part ID is c8x2dorgp40blck610w4bgg4
  • YOUR_PRINTER_ID - click on your printer in the Printers page and look at the url, it should end in /printers/YOUR_PRINTER_ID
  • TAG1 and TAG2 - 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment