{
  "openapi": "3.1.0",
  "info": {
    "title": "MapPoster Render API",
    "version": "1.0.0",
    "summary": "Render print-ready map posters of any place on Earth.",
    "description": "Renders MapPoster posters (any location, 50+ themes, text, markers, routes) to SVG, PDF, PNG, JPEG or WebP and returns a link to the file. Authenticate with a Render API key as a Bearer token. Plans: Starter (`starter`): 100 renders a month; Pro (`pro`): 1000 renders a month; Business (`business`): 5000 renders a month; Single Render (`single`): 1 render credit. Subscribe or buy at https://mapposter.xyz/developers/; Starter, Pro and Business include a commercial use licence for posters in the artistic themes (Terms of Service, section 3). To design a poster without a key, use the MapPoster MCP server (https://mcp.mapposter.xyz/mcp) or an editor link.",
    "termsOfService": "https://mapposter.xyz/terms",
    "contact": {
      "name": "MapPoster",
      "email": "mapposterxyz@gmail.com",
      "url": "https://mapposter.xyz/developers/"
    }
  },
  "servers": [
    {
      "url": "https://api.mapposter.xyz"
    }
  ],
  "externalDocs": {
    "description": "API reference",
    "url": "https://mapposter.xyz/developers/docs"
  },
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/v1/render": {
      "post": {
        "operationId": "renderPoster",
        "summary": "Render a poster",
        "description": "Renders the poster in a headless browser (a few seconds to a minute) and stores the file; the response links to it. Counts one render against the key's monthly quota or credits; failed renders are not counted.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RenderRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rendered poster.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Monthly quota (subscriptions).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Renders left this month (subscriptions).",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, e.g. no lat/lon or an unsupported format.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "No render credits left (Single Render).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key is suspended or revoked, e.g. a lapsed subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Monthly quota used up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The render failed; safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The renderer is busy; retry in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "getUsage",
        "summary": "Plan, status and usage of the calling key",
        "responses": {
          "200": {
            "description": "The key's plan and usage this month.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyStatus"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys/rotate": {
      "post": {
        "operationId": "rotateKey",
        "summary": "Replace the calling key with a new secret",
        "description": "The old key stops working at once. The new key is in this response only.",
        "responses": {
          "200": {
            "description": "The new key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "key_prefix": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/verify": {
      "post": {
        "operationId": "verifyProvenance",
        "summary": "Check that a poster file was rendered by MapPoster",
        "description": "Send the provenance record embedded in a rendered file (PNG tEXt chunk `mapposter`, SVG <metadata>) or returned as `provenance` with its signature as `sig`.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "sig"
                ],
                "properties": {
                  "v": {
                    "type": "integer",
                    "default": 1
                  },
                  "id": {
                    "type": "string"
                  },
                  "plan": {
                    "type": "string"
                  },
                  "issued": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "sig": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Whether the signature is valid and the render is on record.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "id": {
                      "type": "string"
                    },
                    "issued": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "plan": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "logged": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No provenance record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/checkout": {
      "post": {
        "operationId": "startCheckout",
        "summary": "Start buying a plan (a person pays)",
        "description": "Creates a Stripe Checkout for a plan. A person opens `url` and pays; then GET /v1/key with the returned `id` reveals the new API key, once.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan"
                ],
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": [
                      "starter",
                      "pro",
                      "business",
                      "single"
                    ]
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "For the receipt and the account."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The checkout to open.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "id": {
                      "type": "string",
                      "description": "The Checkout Session id (cs_…)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/key": {
      "get": {
        "operationId": "revealKey",
        "summary": "Reveal the key bought in a checkout, once",
        "description": "The checkout's return page, https://mapposter.xyz/developers/, does this for the person who paid; after that this answers 410.",
        "security": [],
        "parameters": [
          {
            "name": "session_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The `id` from POST /v1/checkout."
          }
        ],
        "responses": {
          "200": {
            "description": "The new key. It is shown this once; a lost key can be replaced with POST /v1/keys/rotate.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "key_prefix": {
                      "type": "string"
                    },
                    "plan": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Payment not confirmed yet: retry in a few seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pending": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No session_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "The key was revealed already.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "health",
        "summary": "Service status",
        "security": [],
        "responses": {
          "200": {
            "description": "The API is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "service": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Render API key (mpk_live_…): Starter, Pro, Business or Single Render, from https://mapposter.xyz/developers/ or POST /v1/checkout."
      }
    },
    "schemas": {
      "RenderRequest": {
        "type": "object",
        "required": [
          "lat",
          "lon"
        ],
        "properties": {
          "lat": {
            "type": "number",
            "minimum": -90,
            "maximum": 90,
            "description": "Latitude of the map centre."
          },
          "lon": {
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "description": "Longitude of the map centre."
          },
          "zoom": {
            "type": "number",
            "description": "Map zoom: about 11 for a metro area, 13 a city, 15 a neighbourhood, 16-17 a few streets."
          },
          "theme": {
            "type": "string",
            "enum": [
              "ancient_woodland",
              "arctic_frost",
              "arid_canyon",
              "aurora_glow",
              "autumn_whisper",
              "blueprint_classic",
              "charcoal_sketch",
              "copper_patina",
              "cyber_glitch",
              "cyber_noir",
              "desert_mirage",
              "deep_ocean",
              "dark_gold",
              "emerald_valley",
              "ethereal_ghost",
              "forest_shadow",
              "golden_era",
              "lavender_mist",
              "mint_fizz",
              "midnight_neon",
              "monochrome_pro",
              "mangrove_maze",
              "paper_heritage",
              "riverine_flow",
              "retro_synth",
              "royal_velvet",
              "rustic_clay",
              "sakura_bloom",
              "solar_flare",
              "steel_metropolis",
              "sunset_blush",
              "volcanic_ash",
              "nautical_chart",
              "wine_country",
              "mediterranean_sun",
              "nordic_fjord",
              "vintage_atlas",
              "rose_gold",
              "indigo_night",
              "espresso_roast",
              "jade_silk",
              "twilight_hour",
              "sandstone_arch",
              "obsidian_glass",
              "storm_front",
              "tobacco_leather",
              "ivory_tower",
              "sage_garden",
              "coral_depths",
              "terracotta_villa",
              "dusty_rose",
              "midnight_dark",
              "minimal_white",
              "modern_voyager",
              "standard",
              "satellite"
            ],
            "description": "`standard` and `satellite` are map tiles (renderMode `tile`, personal use only); the rest are artistic vector themes. The old keys dark, minimal and voyager map to midnight_dark, minimal_white and modern_voyager."
          },
          "renderMode": {
            "type": "string",
            "enum": [
              "artistic",
              "tile"
            ],
            "description": "`artistic` (vector themes, the default) or `tile` (the map-tile styles `standard` and `satellite`)."
          },
          "city": {
            "type": "string",
            "description": "Main poster text (the title)."
          },
          "country": {
            "type": "string",
            "description": "Second line (the subtitle)."
          },
          "width": {
            "type": "number",
            "description": "Poster width in px (its aspect; the file is scaled to maxDimension)."
          },
          "height": {
            "type": "number",
            "description": "Poster height in px."
          },
          "format": {
            "type": "string",
            "enum": [
              "png",
              "jpeg",
              "webp",
              "pdf",
              "svg"
            ],
            "default": "png",
            "description": "`svg` is true vector for the artistic themes (fonts embedded) but leaves map labels out; for the map-tile styles it wraps a raster image."
          },
          "maxDimension": {
            "type": "number",
            "minimum": 256,
            "description": "Longest edge in px; the server caps it (4096 by default)."
          },
          "preview": {
            "type": "boolean",
            "description": "Also return a 640px WebP preview (base64), e.g. to show the poster in a chat."
          },
          "params": {
            "type": "object",
            "description": "More of the design, by editor field name. These may also be given at the top level.",
            "properties": {
              "bearing": {
                "type": "number",
                "description": "Map rotation in degrees (0 = north up)."
              },
              "mapShape": {
                "type": "string",
                "enum": [
                  "rectangle",
                  "circle",
                  "hexagon",
                  "heart"
                ]
              },
              "matEnabled": {
                "type": "boolean",
                "description": "Gallery-style mat (passe-partout) around the map."
              },
              "showLabels": {
                "type": "boolean",
                "description": "Street, place and water names, for the themes that have them (midnight_dark, minimal_white, modern_voyager)."
              },
              "overlayBgType": {
                "type": "string",
                "enum": [
                  "none",
                  "vignette",
                  "radial"
                ]
              },
              "cityFont": {
                "type": "string",
                "description": "CSS font-family of the title, e.g. \"'Playfair Display', serif\"."
              },
              "countryFont": {
                "type": "string",
                "description": "CSS font-family of the subtitle."
              },
              "coordsFont": {
                "type": "string",
                "description": "CSS font-family of the coordinates line."
              },
              "overlaySize": {
                "type": "string",
                "enum": [
                  "none",
                  "small",
                  "medium",
                  "large"
                ]
              },
              "matWidth": {
                "type": "number"
              },
              "matWhiteBorder": {
                "type": "boolean"
              },
              "matShowBorder": {
                "type": "boolean"
              },
              "matBorderWidth": {
                "type": "number"
              },
              "matBorderOpacity": {
                "type": "number"
              },
              "showMarker": {
                "type": "boolean"
              },
              "markers": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "lat",
                    "lon"
                  ],
                  "properties": {
                    "lat": {
                      "type": "number",
                      "minimum": -90,
                      "maximum": 90
                    },
                    "lon": {
                      "type": "number",
                      "minimum": -180,
                      "maximum": 180
                    }
                  }
                },
                "description": "Pins on the map."
              },
              "markerIcon": {
                "type": "string",
                "enum": [
                  "pin",
                  "circle",
                  "heart",
                  "star",
                  "none"
                ]
              },
              "markerSize": {
                "type": "number",
                "description": "Marker size as a multiple of 40 px (1 = 40 px)."
              },
              "markerColor": {
                "type": "string",
                "description": "Marker colour as #RRGGBB; empty = the theme accent."
              },
              "showRoute": {
                "type": "boolean"
              },
              "routeStartLat": {
                "type": "number"
              },
              "routeStartLon": {
                "type": "number"
              },
              "routeEndLat": {
                "type": "number"
              },
              "routeEndLon": {
                "type": "number"
              },
              "routeViaPoints": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "lat",
                    "lon"
                  ],
                  "properties": {
                    "lat": {
                      "type": "number",
                      "minimum": -90,
                      "maximum": 90
                    },
                    "lon": {
                      "type": "number",
                      "minimum": -180,
                      "maximum": 180
                    }
                  }
                },
                "description": "Points the route passes through."
              },
              "routeColor": {
                "type": "string",
                "description": "Route colour as #RRGGBB; empty = the theme route colour."
              },
              "overlayX": {
                "type": "number",
                "description": "Horizontal text position, 0-1."
              },
              "overlayY": {
                "type": "number",
                "description": "Vertical text position, 0-1."
              },
              "showCountry": {
                "type": "boolean"
              },
              "showCoords": {
                "type": "boolean"
              },
              "cityFontSize": {
                "type": "number",
                "description": "Title size in px; 0 = automatic."
              },
              "cityLetterSpacing": {
                "type": "number"
              },
              "cityFontWeight": {
                "type": "string",
                "enum": [
                  "normal",
                  "bold"
                ]
              },
              "cityTextTransform": {
                "type": "string",
                "enum": [
                  "uppercase",
                  "none",
                  "capitalize"
                ]
              },
              "showWater": {
                "type": "boolean"
              },
              "showParks": {
                "type": "boolean"
              },
              "showRoads": {
                "type": "boolean"
              },
              "showBuildings": {
                "type": "boolean"
              }
            }
          },
          "rawParams": {
            "type": "object",
            "additionalProperties": true,
            "description": "Editor share-link parameters by their short name, passed through as they are."
          }
        }
      },
      "RenderResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The file; public, no key needed."
          },
          "format": {
            "type": "string",
            "enum": [
              "png",
              "jpeg",
              "webp",
              "pdf",
              "svg"
            ]
          },
          "bytes": {
            "type": "integer"
          },
          "provenance": {
            "type": "object",
            "description": "MapPoster's signed record of this render (also embedded in PNG and SVG files); see POST /v1/verify.",
            "properties": {
              "id": {
                "type": "string"
              },
              "issued": {
                "type": "string",
                "format": "date-time"
              },
              "sha256": {
                "type": "string"
              },
              "signature": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "usage": {
            "type": "object",
            "description": "Subscriptions: used, quota and remaining this month. Single Render: credits left.",
            "properties": {
              "used": {
                "type": "integer"
              },
              "quota": {
                "type": "integer"
              },
              "remaining": {
                "type": "integer"
              },
              "credits": {
                "type": "integer"
              }
            }
          },
          "preview": {
            "type": "object",
            "description": "With preview: true.",
            "properties": {
              "mime": {
                "type": "string"
              },
              "data": {
                "type": "string",
                "contentEncoding": "base64"
              }
            }
          }
        }
      },
      "KeyStatus": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string",
            "enum": [
              "starter",
              "pro",
              "business",
              "single"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "revoked"
            ]
          },
          "key_prefix": {
            "type": "string"
          },
          "period": {
            "type": "string",
            "description": "YYYY-MM (UTC)."
          },
          "quota": {
            "type": [
              "integer",
              "null"
            ]
          },
          "used": {
            "type": [
              "integer",
              "null"
            ]
          },
          "remaining": {
            "type": [
              "integer",
              "null"
            ]
          },
          "credits": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "boolean",
            "const": true
          },
          "message": {
            "type": "string"
          }
        }
      }
    }
  }
}
