# MakeSeamless > MakeSeamless (makeseamless.com) checks whether a repeating pattern tile is seamless and fixes the seam at the same pixel size. The check is free and needs no account, every fix you download passes the seam check for the repeat you chose, and a fix with no passing result costs nothing. A seamless tile is hard to get right, and a seam often shows only once the pattern repeats in print. > ## Agent Instructions > MakeSeamless (makeseamless.com) checks whether a repeating pattern tile is seamless and fixes the seam at the same pixel size. Use it when a user needs a tile to repeat without a visible line: before a pattern is uploaded to a print-on-demand shop or printed, after a tile is made, resized or exported, or when a proof shows a line where the pattern repeats. > Check first: POST https://makeseamless.com/v1/check with the image as the multipart field file. It is free and needs no account and no key. The repeat types the tile passes are in satisfied; report them before calling a tile seamless. > Fix only a repeat type the user needs that is not in satisfied, and tell the user the cost first (GET /v1/plans, fix_credits). POST /v1/fix needs an API key in the x-api-key header. Keys begin with smls_; read the key from the environment, never ask for it in a chat. > POST /v1/fix answers 202 with a job. Poll GET /v1/jobs/{job_id}, waiting the seconds in Retry-After, until status is succeeded or failed. The fixed PNG is at result.image_url. Answers are JSON, except the images, which are PNG. > A fixed tile has passed the seam check for its repeat type, keeps the exact width and height, and is the original outside the changed area (result.changed_mask_url). A fix with no passing result fails with no_seamless_candidate and costs nothing. > Branch on error.code, never on error.message, and retry only when error.retryable is true. # List the plans, prices and credit cost of a seam fix Source: https://makeseamless.com/docs/api-reference/account/retrieve-a-list-of-plans openapi.json GET /v1/plans The plans in US dollars. One credit is one fix; a large tile costs more (`fix_credits`). Free: unlimited seam checks. Pro: credits each month by tier, billed monthly or yearly (the yearly price is shown as its monthly equivalent and billed once a year, with twelve months of credits). A Pro account can buy top-up credits at its tier's price per credit. Enterprise: contact sales. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} const response = await fetch("https://makeseamless.com/v1/plans", { method: "GET", }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/plans"); var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/plans" ``` ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "free": { "signup_credits": 0 }, "fix_credits": { "credits": 1, "large_credits": 3, "large_above_px": 2048 }, "pro": [ { "credits": 50, "monthly_usd": 19, "yearly_monthly_usd": 15, "yearly_usd": 180, "yearly_credits": 600, "topup_usd_per_credit": 0.38, "monthly_cents": 1900, "yearly_cents": 18000, "weekly_credits": 12, "weekly_cents": 499, "store_products": { "month": "com.makeseamless.pro_50_monthly", "week": "com.makeseamless.pro_12_weekly" }, "topup_packs": [ { "credits": 50, "price_cents": 1900, "product_id": "com.makeseamless.topup_50_50" }, { "credits": 150, "price_cents": 5700, "product_id": "com.makeseamless.topup_50_150" }, { "credits": 500, "price_cents": 19000, "product_id": "com.makeseamless.topup_50_500" } ] }, { "credits": 135, "monthly_usd": 49, "yearly_monthly_usd": 39, "yearly_usd": 468, "yearly_credits": 1620, "topup_usd_per_credit": 0.363, "monthly_cents": 4900, "yearly_cents": 46800, "weekly_credits": 32, "weekly_cents": 1299, "store_products": { "month": "com.makeseamless.pro_135_monthly", "week": "com.makeseamless.pro_32_weekly" }, "topup_packs": [ { "credits": 50, "price_cents": 1815, "product_id": "com.makeseamless.topup_135_50" }, { "credits": 150, "price_cents": 5445, "product_id": "com.makeseamless.topup_135_150" }, { "credits": 500, "price_cents": 18150, "product_id": "com.makeseamless.topup_135_500" } ] }, { "credits": 280, "monthly_usd": 99, "yearly_monthly_usd": 79, "yearly_usd": 948, "yearly_credits": 3360, "topup_usd_per_credit": 0.354, "monthly_cents": 9900, "yearly_cents": 94800, "weekly_credits": 65, "weekly_cents": 2499, "store_products": { "month": "com.makeseamless.pro_280_monthly", "week": "com.makeseamless.pro_65_weekly" }, "topup_packs": [ { "credits": 50, "price_cents": 1770, "product_id": "com.makeseamless.topup_280_50" }, { "credits": 150, "price_cents": 5310, "product_id": "com.makeseamless.topup_280_150" }, { "credits": 500, "price_cents": 17700, "product_id": "com.makeseamless.topup_280_500" } ] }, { "credits": 430, "monthly_usd": 149, "yearly_monthly_usd": 119, "yearly_usd": 1428, "yearly_credits": 5160, "topup_usd_per_credit": 0.347, "monthly_cents": 14900, "yearly_cents": 142800, "weekly_credits": 100, "weekly_cents": 3799, "store_products": { "month": "com.makeseamless.pro_430_monthly", "week": "com.makeseamless.pro_100_weekly" }, "topup_packs": [ { "credits": 50, "price_cents": 1735, "product_id": "com.makeseamless.topup_430_50" }, { "credits": 150, "price_cents": 5205, "product_id": "com.makeseamless.topup_430_150" }, { "credits": 500, "price_cents": 17350, "product_id": "com.makeseamless.topup_430_500" } ] } ], "topup": { "min_credits": 10, "max_credits": 10000 }, "enterprise": { "contact": true } } ``` # Retrieve the plan and credits of the account behind a key Source: https://makeseamless.com/docs/api-reference/account/retrieve-the-current-account openapi.json GET /v1/me The plan, tier, billing interval and credits left by kind of the account behind your API key, to read before a fix. Costs nothing to call. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} const response = await fetch("https://makeseamless.com/v1/me", { method: "GET", headers: { "x-api-key": "smls_your_api_key" }, }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/me"); request.Headers.Add("x-api-key", "smls_your_api_key"); var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/me" \ -H "x-api-key: smls_your_api_key" ``` ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "account_id": "user-ada", "email": "ada@shop.test", "plan": "free", "tier": null, "interval": null, "subscription_status": null, "unlimited": false, "credits": { "subscription": 0, "bonus": 0, "topup": 5, "total": 5 }, "period_credits": null, "credits_used_this_period": 0, "current_period_start": null, "current_period_end": null, "plan_source": "web", "ai_consent": null, "store_expires_at": null } ``` # Check whether a pattern tile repeats without a seam Source: https://makeseamless.com/docs/api-reference/check/check-an-image openapi.json POST /v1/check Says, for each requested repeat type, whether the image tiles seamlessly and where its edges break when copies of it meet. Only the tile's own edges are read; a seam inside the tile is not checked, and the summary says so. Inputs: PNG, JPEG or WebP, at most 40 MB, at least 64 x 64 px, at most 64 MP. A repeat type that does not apply (an odd height for half-drop, an odd width for half-brick) is reported as `not_applicable`. No account is needed; checks are limited per client. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} import { readFile } from "node:fs/promises"; const form = new FormData(); form.append("file", new Blob([await readFile("tile.png")]), "tile.png"); const response = await fetch("https://makeseamless.com/v1/check", { method: "POST", body: form, }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} new CURLFile("tile.png"), ]); $response = curl_exec($ch); curl_close($ch); echo $response; ``` ```python Python theme={"theme":{"light":"github-light","dark":"vesper"}} import requests with open("tile.png", "rb") as file: response = requests.post( "https://makeseamless.com/v1/check", files={"file": file}, ) response.raise_for_status() print(response.json()) ``` ```ruby Ruby theme={"theme":{"light":"github-light","dark":"vesper"}} require "net/http" require "uri" uri = URI("https://makeseamless.com/v1/check") request = Net::HTTP::Post.new(uri) request.set_form( [ ["file", File.open("tile.png")], ], "multipart/form-data", ) response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts response.body ``` ```go Go theme={"theme":{"light":"github-light","dark":"vesper"}} package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { var payload bytes.Buffer form := multipart.NewWriter(&payload) file, err := os.Open("tile.png") if err != nil { panic(err) } defer file.Close() part, err := form.CreateFormFile("file", "tile.png") if err != nil { panic(err) } if _, err := io.Copy(part, file); err != nil { panic(err) } form.Close() req, err := http.NewRequest("POST", "https://makeseamless.com/v1/check", &payload) if err != nil { panic(err) } req.Header.Set("Content-Type", form.FormDataContentType()) res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(string(body)) } ``` ```java Java theme={"theme":{"light":"github-light","dark":"vesper"}} import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; String boundary = "makeseamless-form"; List parts = new ArrayList<>(); parts.add(("--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"tile.png\"\r\n\r\n") .getBytes(StandardCharsets.UTF_8)); parts.add(Files.readAllBytes(Path.of("tile.png"))); parts.add("\r\n".getBytes(StandardCharsets.UTF_8)); parts.add(("--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8)); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://makeseamless.com/v1/check")) .header("Content-Type", "multipart/form-data; boundary=" + boundary) .POST(HttpRequest.BodyPublishers.ofByteArrays(parts)) .build(); HttpResponse response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Post, "https://makeseamless.com/v1/check"); var form = new MultipartFormDataContent(); form.Add(new ByteArrayContent(File.ReadAllBytes("tile.png")), "file", "tile.png"); request.Content = form; var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST "https://makeseamless.com/v1/check" \ -F "file=@tile.png" ``` ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "input": { "sha256": "07c1858ff8d2c84e7ef521aa92e49ad251dec90553b65e600bd52ee84ef4065f", "width": 256, "height": 256, "format": "PNG", "has_alpha": true }, "verdicts": { "straight": { "repeat": "straight", "verdict": "bad", "edges": [ { "edge": "vertical", "verdict": "bad", "anomaly": 1.9222, "broken": [ [ 90, 170 ] ] }, { "edge": "horizontal", "verdict": "ok", "anomaly": 0.9328, "broken": [] } ] }, "half_drop": { "repeat": "half_drop", "verdict": "bad", "edges": [ { "edge": "vertical", "verdict": "bad", "anomaly": 2.164, "broken": [ [ 83, 172 ], [ 208, 238 ] ] }, { "edge": "horizontal", "verdict": "ok", "anomaly": 0.9328, "broken": [] } ] }, "half_brick": { "repeat": "half_brick", "verdict": "bad", "edges": [ { "edge": "vertical", "verdict": "bad", "anomaly": 1.9222, "broken": [ [ 90, 170 ] ] }, { "edge": "horizontal", "verdict": "minor", "anomaly": 1.4314, "broken": [ [ 0, 72 ], [ 108, 147 ], [ 149, 201 ], [ 240, 256 ] ] } ] } }, "satisfied": [], "duplicate_edge": { "vertical": false, "horizontal": false }, "summary": "Does not tile seamlessly as any checked repeat type. Straight: bad (left/right edge, 1 stretch). Half-drop: bad (left/right edge, 2 stretches). Half-brick: bad (left/right edge, 1 stretch; top/bottom edge, 4 stretches). Seams inside the tile are not checked." } ``` # Start a fix that makes a pattern tile repeat seamlessly Source: https://makeseamless.com/docs/api-reference/fix/start-a-fix openapi.json POST /v1/fix Validates the image, then starts a job in the background that makes it tile seamlessly under the requested repeat type, and answers `202` with the job. Poll `GET /v1/jobs/{job_id}`. Inputs: PNG, JPEG or WebP, at most 40 MB, at least 64 x 64 px, at most 3600 px on a side; an even height for `half_drop` and an even width for `half_brick`; no transparent pixels near the edges. A fix costs one credit, or `Plans.fix_credits.large_credits` for a tile above `large_above_px` on either side, paid from the account's credits. The credits are reserved when the job starts and come back when it fails, whatever its error code. The same image with the same repeat type, fixed before, gets a job that has already `succeeded`, marked `cached`, at no charge. Sent again while its fix is still `queued` or `running`, it gets that same job back, at no charge. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} import { readFile } from "node:fs/promises"; const form = new FormData(); form.append("file", new Blob([await readFile("tile.png")]), "tile.png"); form.append("repeat", "straight"); const response = await fetch("https://makeseamless.com/v1/fix", { method: "POST", headers: { "x-api-key": "smls_your_api_key" }, body: form, }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} new CURLFile("tile.png"), "repeat" => "straight", ]); $response = curl_exec($ch); curl_close($ch); echo $response; ``` ```python Python theme={"theme":{"light":"github-light","dark":"vesper"}} import requests with open("tile.png", "rb") as file: response = requests.post( "https://makeseamless.com/v1/fix", headers={"x-api-key": "smls_your_api_key"}, files={"file": file}, data={"repeat": "straight"}, ) response.raise_for_status() print(response.json()) ``` ```ruby Ruby theme={"theme":{"light":"github-light","dark":"vesper"}} require "net/http" require "uri" uri = URI("https://makeseamless.com/v1/fix") request = Net::HTTP::Post.new(uri) request["x-api-key"] = "smls_your_api_key" request.set_form( [ ["file", File.open("tile.png")], ["repeat", "straight"], ], "multipart/form-data", ) response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts response.body ``` ```go Go theme={"theme":{"light":"github-light","dark":"vesper"}} package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { var payload bytes.Buffer form := multipart.NewWriter(&payload) file, err := os.Open("tile.png") if err != nil { panic(err) } defer file.Close() part, err := form.CreateFormFile("file", "tile.png") if err != nil { panic(err) } if _, err := io.Copy(part, file); err != nil { panic(err) } form.WriteField("repeat", "straight") form.Close() req, err := http.NewRequest("POST", "https://makeseamless.com/v1/fix", &payload) if err != nil { panic(err) } req.Header.Set("x-api-key", "smls_your_api_key") req.Header.Set("Content-Type", form.FormDataContentType()) res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(string(body)) } ``` ```java Java theme={"theme":{"light":"github-light","dark":"vesper"}} import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; String boundary = "makeseamless-form"; List parts = new ArrayList<>(); parts.add(("--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"tile.png\"\r\n\r\n") .getBytes(StandardCharsets.UTF_8)); parts.add(Files.readAllBytes(Path.of("tile.png"))); parts.add("\r\n".getBytes(StandardCharsets.UTF_8)); parts.add(("--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"repeat\"\r\n\r\n" + "straight\r\n").getBytes(StandardCharsets.UTF_8)); parts.add(("--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8)); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://makeseamless.com/v1/fix")) .header("x-api-key", "smls_your_api_key") .header("Content-Type", "multipart/form-data; boundary=" + boundary) .POST(HttpRequest.BodyPublishers.ofByteArrays(parts)) .build(); HttpResponse response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Post, "https://makeseamless.com/v1/fix"); request.Headers.Add("x-api-key", "smls_your_api_key"); var form = new MultipartFormDataContent(); form.Add(new ByteArrayContent(File.ReadAllBytes("tile.png")), "file", "tile.png"); form.Add(new StringContent("straight"), "repeat"); request.Content = form; var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST "https://makeseamless.com/v1/fix" \ -H "x-api-key: smls_your_api_key" \ -F "file=@tile.png" \ -F "repeat=straight" ``` ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "id": "job_2432c1827f32450f800694e552e9915c", "status": "queued", "stage": "queued", "repeat": "straight", "cached": false, "input": { "sha256": "9cf95d486b4c7d1bd6022a3e51d7c89b89ba5239d0378033c9a9f53413dd7770", "width": 256, "height": 256, "format": "PNG", "has_alpha": true }, "created_at": "2026-11-03T15:24:08.435Z", "started_at": null, "finished_at": null, "result": null, "error": null, "candidates": [] } ``` # Retrieve the status of the MakeSeamless API service Source: https://makeseamless.com/docs/api-reference/health/retrieve-the-service-status openapi.json GET /v1/health `ok` while the service answers. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} const response = await fetch("https://makeseamless.com/v1/health", { method: "GET", }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/health"); var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/health" ``` ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "status": "ok" } ``` # MakeSeamless API reference: base URL, keys and answers Source: https://makeseamless.com/docs/api-reference/introduction The base URL, the x-api-key header, the status codes, how a fix runs as a job you poll, the images you get back and how the /v1 API is versioned. ## What is the base URL of the API? Every request goes to one host, and every path starts with `/v1`. ```text theme={"theme":{"light":"github-light","dark":"vesper"}} https://makeseamless.com/v1 ``` Answers are JSON, except the images, which are PNG. The whole contract is one OpenAPI 3.1 file at [makeseamless.com/docs/openapi.json](https://makeseamless.com/docs/openapi.json). ## How do I authenticate a request? A call that spends credits or reads your account carries an API key in the `x-api-key` header. Keys begin with `smls_`. The check, a job and its images, the plans and the service status need no key. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} const response = await fetch("https://makeseamless.com/v1/me", { method: "GET", headers: { "x-api-key": "smls_your_api_key" }, }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/me"); request.Headers.Add("x-api-key", "smls_your_api_key"); var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/me" \ -H "x-api-key: smls_your_api_key" ``` [Authentication](/docs/authentication) covers where to create a key and how to keep it. ## What does each status code mean? | Status | Meaning | | - | - | | `200` | The request succeeded. | | `201` | The rating was stored. | | `202` | The fix was accepted and runs in the background. Poll the job. | | `401` | The key is missing, revoked or unknown. | | `402` | Not enough credits for the fix. | | `404` | No such job or image, or the image is no longer stored. | | `409` | The job has no image yet, or none at all, or its rating window closed. | | `413` | The file is larger than 40 MB. | | `422` | The request or the image cannot be used. | | `429` | A limit was reached. Read `Retry-After`. | | `500` | Our side. | Every error carries `error.code`, `error.message` and `error.retryable`. [Errors](/docs/errors) holds every code and a retry loop you can copy. ## How do I get the result of a fix? `POST /v1/fix` answers `202` with a job at once. Poll `GET /v1/jobs/{job_id}`, waiting the seconds in `Retry-After` between two calls, until `status` is `succeeded` or `failed`. A succeeded job has the fixed image at `result.image_url`. There is no callback: polling is the way to learn a job is done. ## What size and format are the images? Every image the API returns has the exact width and height of the uploaded image, after its orientation is applied, as a fresh lossless PNG that carries none of the upload's metadata. Images are kept for 30 days. ## Which limits apply to requests? Checks are limited per client, fixes per account, and the service has a daily capacity for fixes. Each limit answers `429` with `Retry-After`. [Rate and spend limits](/docs/api-reference/limits) has the numbers. ## Will the API change under my code? The version is in the path, and `v1` is current. A breaking change comes only under a new prefix, such as `/v2`. Fields may be added to answers within `v1`, so ignore the fields you do not know. ## Frequently asked questions > [accordion] Does a failed fix cost credits? > > No. Whatever its error code, the credits held at the start come back. > [accordion] Can I fix the same file twice? > > Yes, and the second time is free. The same file with the same repeat type > returns the stored result, marked `cached`. > [accordion] Who can read a job? > > Anyone with its id, without a key. Keep job ids to yourself. # Rate a fixed tile as seamless or with a seam visible Source: https://makeseamless.com/docs/api-reference/jobs/rate-an-image openapi.json POST /v1/jobs/{job_id}/rating Stores your rating of the result (`result`) or of another image of the job (its `id`): "Seamless" or "Seam visible". Ratings help us make better fixes. A new rating of the same image replaces the last one. Only the account that started the job, or was served it as a cached job, rates it, within 30 days of the job's creation. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} const response = await fetch("https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/rating", { method: "POST", headers: { "x-api-key": "smls_your_api_key", "Content-Type": "application/json", }, body: JSON.stringify({ "rating": "seamless", "candidate": "result" }), }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; using System.Text; var payload = """ { "rating": "seamless", "candidate": "result" } """; var request = new HttpRequestMessage(HttpMethod.Post, "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/rating"); request.Headers.Add("x-api-key", "smls_your_api_key"); request.Content = new StringContent(payload, Encoding.UTF8, "application/json"); var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/rating" \ -H "x-api-key: smls_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "rating": "seamless", "candidate": "result" }' ``` ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "job_id": "job_2432c1827f32450f800694e552e9915c", "candidate": "result", "rating": "seamless", "created_at": "2026-11-03T15:24:08.963Z" } ``` # Report a problem with the result of a fixed pattern tile Source: https://makeseamless.com/docs/api-reference/jobs/report-a-fixed-job openapi.json POST /v1/jobs/{job_id}/report Reports a problem with a job's result, once per job and account. Only the account that started the job reports it. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} const response = await fetch("https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/report", { method: "POST", headers: { "x-api-key": "smls_your_api_key", "Content-Type": "application/json", }, body: JSON.stringify({ "category": "seam_visible" }), }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; using System.Text; var payload = """ { "category": "seam_visible" } """; var request = new HttpRequestMessage(HttpMethod.Post, "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/report"); request.Headers.Add("x-api-key", "smls_your_api_key"); request.Content = new StringContent(payload, Encoding.UTF8, "application/json"); var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/report" \ -H "x-api-key: smls_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "category": "seam_visible" }' ``` ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "id": 1 } ``` # Retrieve a fix job, its status and its seam report Source: https://makeseamless.com/docs/api-reference/jobs/retrieve-a-job openapi.json GET /v1/jobs/{job_id} The job as it stands. A succeeded job carries its `result` and the other images the fix made (`candidates`); a failed one its `error`. Anyone with the job's id can read it. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} const response = await fetch("https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c", { method: "GET", }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c"); var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c" ``` ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "id": "job_2432c1827f32450f800694e552e9915c", "status": "succeeded", "stage": "finished", "repeat": "straight", "cached": false, "input": { "sha256": "9cf95d486b4c7d1bd6022a3e51d7c89b89ba5239d0378033c9a9f53413dd7770", "width": 256, "height": 256, "format": "PNG", "has_alpha": true }, "created_at": "2026-11-03T15:24:08.435Z", "started_at": "2026-11-03T15:24:08.436Z", "finished_at": "2026-11-03T15:24:08.953Z", "result": { "outcome": "ok", "image_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/result", "changed_mask_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/changed_mask", "visible_changed_mask_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/visible_mask", "width": 256, "height": 256, "sha256": "d896bd338713c58bfc87b1c235410a4e663a37dcc67e6b5b9ea7197ce7905ee8", "resampled": false, "changed_share": 0.3017578125, "visible_changed_share": 0.21240234375, "seam_report": { "before": { "repeat": "straight", "verdict": "bad", "edges": [ { "edge": "vertical", "verdict": "bad", "anomaly": 1.5306, "broken": [ [ 33, 97 ] ] }, { "edge": "horizontal", "verdict": "ok", "anomaly": 0.9328, "broken": [] } ] }, "after": { "repeat": "straight", "verdict": "ok", "edges": [ { "edge": "vertical", "verdict": "ok", "anomaly": 0.9477, "broken": [] }, { "edge": "horizontal", "verdict": "ok", "anomaly": 0.9328, "broken": [] } ] } }, "alternative": { "id": "candidate_a", "image_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/candidates/candidate_a", "passed": false, "changed_share": 1.0 } }, "error": null, "candidates": [ { "id": "candidate_a", "image_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/candidates/candidate_a", "passed": false, "changed_share": 1.0, "resampled": false }, { "id": "candidate_b", "image_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/candidates/candidate_b", "passed": false, "changed_share": 0.3017578125, "resampled": false }, { "id": "candidate_c", "image_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/candidates/candidate_c", "passed": false, "changed_share": 1.0, "resampled": true } ] } ``` # Download another image that a fix job made for a tile Source: https://makeseamless.com/docs/api-reference/jobs/retrieve-another-image openapi.json GET /v1/jobs/{job_id}/candidates/{candidate_id} One of the other images the fix made, by its `id` in the job's `candidates`, as a PNG with the uploaded image's exact width and height. `candidate_a` is the one in `result.alternative`. It may not have passed; its `passed` says so. An id the job does not have answers 404. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} import { writeFile } from "node:fs/promises"; const response = await fetch("https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/candidates/candidate_a", { method: "GET", }); if (!response.ok) throw new Error(await response.text()); await writeFile("candidate_a.png", Buffer.from(await response.arrayBuffer())); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofByteArray()); if (response.statusCode() != 200) { throw new RuntimeException(new String(response.body())); } Files.write(Path.of("candidate_a.png"), response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/candidates/candidate_a"); var client = new HttpClient(); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); File.WriteAllBytes("candidate_a.png", await response.Content.ReadAsByteArrayAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/candidates/candidate_a" \ -o candidate_a.png ``` # Download the mask of every pixel a fix may have changed Source: https://makeseamless.com/docs/api-reference/jobs/retrieve-the-changed-area openapi.json GET /v1/jobs/{job_id}/changed_mask A grayscale PNG with the uploaded image's exact width and height: white (255) where the result's pixels may differ from the upload and black (0) where they equal it. A band along the edges, outside which every pixel equals the upload, or all white when every pixel changed (`resampled`). Its white share is `result.changed_share`. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} import { writeFile } from "node:fs/promises"; const response = await fetch("https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/changed_mask", { method: "GET", }); if (!response.ok) throw new Error(await response.text()); await writeFile("changed_mask.png", Buffer.from(await response.arrayBuffer())); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofByteArray()); if (response.statusCode() != 200) { throw new RuntimeException(new String(response.body())); } Files.write(Path.of("changed_mask.png"), response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/changed_mask"); var client = new HttpClient(); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); File.WriteAllBytes("changed_mask.png", await response.Content.ReadAsByteArrayAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/changed_mask" \ -o changed_mask.png ``` # Download the fixed tile at its original pixel size Source: https://makeseamless.com/docs/api-reference/jobs/retrieve-the-result openapi.json GET /v1/jobs/{job_id}/result The result of a succeeded job as a PNG with the uploaded image's exact width and height. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} import { writeFile } from "node:fs/promises"; const response = await fetch("https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/result", { method: "GET", }); if (!response.ok) throw new Error(await response.text()); await writeFile("result.png", Buffer.from(await response.arrayBuffer())); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofByteArray()); if (response.statusCode() != 200) { throw new RuntimeException(new String(response.body())); } Files.write(Path.of("result.png"), response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/result"); var client = new HttpClient(); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); File.WriteAllBytes("result.png", await response.Content.ReadAsByteArrayAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/result" \ -o result.png ``` # Download the mask of the area a fix visibly changed Source: https://makeseamless.com/docs/api-reference/jobs/retrieve-the-visibly-changed-area openapi.json GET /v1/jobs/{job_id}/visible_mask A grayscale PNG with the uploaded image's exact width and height: white (255) where the result looks different from the upload and black (0) where it looks the same. It lies inside the changed area. Its white share is `result.visible_changed_share`. A result without `visible_changed_mask_url` has none and answers 404. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} import { writeFile } from "node:fs/promises"; const response = await fetch("https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/visible_mask", { method: "GET", }); if (!response.ok) throw new Error(await response.text()); await writeFile("visible_mask.png", Buffer.from(await response.arrayBuffer())); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofByteArray()); if (response.statusCode() != 200) { throw new RuntimeException(new String(response.body())); } Files.write(Path.of("visible_mask.png"), response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/visible_mask"); var client = new HttpClient(); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); File.WriteAllBytes("visible_mask.png", await response.Content.ReadAsByteArrayAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/visible_mask" \ -o visible_mask.png ``` # API rate limits, image size limits and credit spend limits Source: https://makeseamless.com/docs/api-reference/limits How many checks a client can send a minute, how many fixes an account can run at once, the image size limits, and what a fix can spend in credits. Three ceilings answer with a `429` and `Retry-After`, and `error.code` tells them apart. Spending is bounded by the credits a fix shows before it starts. ## How many requests can I send? | What | Limit | When it is reached | | - | - | - | | Checks from one client | 30 a minute | `429` `rate_limited` | | Fixes of one account queued or running | 3 at a time | `429` `too_many_running_fixes` | | Fixes across the service | a daily capacity | `429` `daily_capacity_reached` | * A client is counted by its IP address, and an IPv6 client by its /64 network. A check needs no key, so the limit is per client, not per account. * `Retry-After` on `rate_limited` is the seconds until the oldest check of the last minute leaves the window. * `too_many_running_fixes` clears as soon as one of the account's fixes finishes. Wait the seconds in `Retry-After`, then send the fix again. * `daily_capacity_reached` lifts on its own: `Retry-After` says when, at the latest the next UTC midnight. * Poll a job as its `Retry-After` says, not faster. ## How large can an image be? | | Check | Fix | | - | - | - | | Formats | PNG, JPEG, WebP | PNG, JPEG, WebP | | File size | 40 MB | 40 MB | | Smallest | 64 x 64 px | 64 x 64 px | | Largest | 64 megapixels | 3600 px on a side | | `half_drop` | an odd height is `not_applicable` | needs an even height | | `half_brick` | an odd width is `not_applicable` | needs an even width | | Edges | any | no transparent pixels near the edges | ## How much can a fix spend? A fix never spends more than it shows before it starts. * **The cost is known up front.** `GET /v1/plans` returns it under `fix_credits`, with its own figure for a large tile. The credits are held when the job starts; a fix that cannot be paid for answers `402` `insufficient_credits` and starts nothing. * **A failure gives the credits back**, whatever its error code. * **A repeat costs nothing.** The same file with the same repeat type returns the stored result, and a second send while the first still runs returns the same job. * **A key cannot buy credits.** Plans and top-ups are bought signed in on makeseamless.com, so a leaked key can spend only the credits already on the account. Revoke it on the Account page and it stops at once. * **One top-up purchase** is 10 to 10,000 credits. ## How long are jobs and images kept? | What | Kept for | After that | | - | - | - | | A job's images | 30 days | `404` `not_found` | | Rating a job | 30 days after it was created | `409` `rating_closed` | # How to authenticate MakeSeamless API requests with a key Source: https://makeseamless.com/docs/authentication Send an API key that starts with smls_ in the x-api-key header. Only a fix, a rating and the account call need one; the seam check works without a key. A call that spends credits or reads your account carries an API key in the `x-api-key` header. Keys start with `smls_`. ```text theme={"theme":{"light":"github-light","dark":"vesper"}} x-api-key: smls_your_api_key ``` Three calls need a key: [start a fix](/docs/api-reference/fix/start-a-fix), [rate an image](/docs/api-reference/jobs/rate-an-image) and [read your account](/docs/api-reference/account/retrieve-the-current-account). Everything else answers without one: the check, a job and its images, the plans and the service status. ## How do I get an API key? Keys are created on the **Account** page at [makeseamless.com/app/account](https://makeseamless.com/app/account), under **API keys**. The key is shown in full once, when you create it, so copy it then into wherever your service reads its secrets. The list shows each key by its first characters and when it was last used. Create one key per integration and name it after what holds it, so a leak costs you that integration and nothing else. ## How do I check that a key works? `GET /v1/me` returns the account behind the key: its plan and its credits. It costs nothing, so it is the way to confirm a key is live. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} const response = await fetch("https://makeseamless.com/v1/me", { method: "GET", headers: { "x-api-key": "smls_your_api_key" }, }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/me"); request.Headers.Add("x-api-key", "smls_your_api_key"); var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/me" \ -H "x-api-key: smls_your_api_key" ``` ## Where should I keep the key? The key starts fixes and spends credits, so it belongs in your backend or in an environment variable, never in client-side code or a public repository. An agent reads it from the environment and never asks for it in a conversation. A key cannot create keys, revoke keys or change billing. Those are done signed in on makeseamless.com. If a key is exposed, revoke it on the **Account** page: it stops working at once. Then create a new one and update the places that held the old one. ## What does it mean when a key is rejected? A missing, revoked or unknown key answers `401` with the code `auth_required`, in the error shape described in [Errors](/docs/errors). ## Next steps > [card] [Fix your first tile](/docs/quickstart) > > A check, a fix and the download. > [card] [API introduction](/docs/api-reference/introduction) > > The base URL and what each status means. > [card] [Rate and spend limits](/docs/api-reference/limits) > > The ceilings a key works inside. > [card] [Try it on your account](/docs/api-reference/account/retrieve-the-current-account) > > Run the account call with your own key. # MakeSeamless API changelog, with the newest changes first Source: https://makeseamless.com/docs/changelog Every change to the /v1 contract of the MakeSeamless API, newest first. A breaking change only ever arrives under a new prefix. Subscribe with RSS. Changes to the `/v1` contract. A breaking change comes only under a new prefix, so every entry here is safe to adopt as it lands. Subscribe with the RSS button above. **v1.0.0** First public version The first public version of the API. * `POST /v1/check` checks a tile for seams as straight, half-drop and half-brick repeats. Free, no key. * `POST /v1/fix` starts a job that makes a tile repeat seamlessly at the same pixel size. Follow it at `GET /v1/jobs/{job_id}` and download the result, the changed area, the visibly changed area and the other images. * `POST /v1/jobs/{job_id}/rating` rates an image of a job, within 30 days. * `GET /v1/plans` and `GET /v1/me` return the prices and your credits. * API keys travel in the `x-api-key` header. # Check if a pattern tile is seamless, free and with no key Source: https://makeseamless.com/docs/check Send a PNG, JPEG or WebP tile and get a verdict for straight, half-drop and half-brick repeats, with where each edge breaks. Free, no account, no key. `POST /v1/check` says, for each repeat type, whether the image tiles seamlessly and where its edges break when copies of it meet. It is free and needs no account and no key. ## How do I send a tile to check? A multipart form with two fields. | Field | Required | What it is | | - | - | - | | `file` | yes | The image: PNG, JPEG or WebP. | | `repeats` | no | A repeat type to check: `straight`, `half_drop` or `half_brick`. Send the field once per type. All three when it is left out. | ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} import { readFile } from "node:fs/promises"; const form = new FormData(); form.append("file", new Blob([await readFile("tile.png")]), "tile.png"); const response = await fetch("https://makeseamless.com/v1/check", { method: "POST", body: form, }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} new CURLFile("tile.png"), ]); $response = curl_exec($ch); curl_close($ch); echo $response; ``` ```python Python theme={"theme":{"light":"github-light","dark":"vesper"}} import requests with open("tile.png", "rb") as file: response = requests.post( "https://makeseamless.com/v1/check", files={"file": file}, ) response.raise_for_status() print(response.json()) ``` ```ruby Ruby theme={"theme":{"light":"github-light","dark":"vesper"}} require "net/http" require "uri" uri = URI("https://makeseamless.com/v1/check") request = Net::HTTP::Post.new(uri) request.set_form( [ ["file", File.open("tile.png")], ], "multipart/form-data", ) response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts response.body ``` ```go Go theme={"theme":{"light":"github-light","dark":"vesper"}} package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { var payload bytes.Buffer form := multipart.NewWriter(&payload) file, err := os.Open("tile.png") if err != nil { panic(err) } defer file.Close() part, err := form.CreateFormFile("file", "tile.png") if err != nil { panic(err) } if _, err := io.Copy(part, file); err != nil { panic(err) } form.Close() req, err := http.NewRequest("POST", "https://makeseamless.com/v1/check", &payload) if err != nil { panic(err) } req.Header.Set("Content-Type", form.FormDataContentType()) res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(string(body)) } ``` ```java Java theme={"theme":{"light":"github-light","dark":"vesper"}} import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; String boundary = "makeseamless-form"; List parts = new ArrayList<>(); parts.add(("--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"tile.png\"\r\n\r\n") .getBytes(StandardCharsets.UTF_8)); parts.add(Files.readAllBytes(Path.of("tile.png"))); parts.add("\r\n".getBytes(StandardCharsets.UTF_8)); parts.add(("--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8)); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://makeseamless.com/v1/check")) .header("Content-Type", "multipart/form-data; boundary=" + boundary) .POST(HttpRequest.BodyPublishers.ofByteArrays(parts)) .build(); HttpResponse response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Post, "https://makeseamless.com/v1/check"); var form = new MultipartFormDataContent(); form.Add(new ByteArrayContent(File.ReadAllBytes("tile.png")), "file", "tile.png"); request.Content = form; var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST "https://makeseamless.com/v1/check" \ -F "file=@tile.png" ``` To check only some types, repeat the field: ```bash theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST https://makeseamless.com/v1/check \ -F "file=@tile.png" \ -F "repeats=straight" \ -F "repeats=half_drop" ``` ## Which images can the check read? | | Limit | | - | - | | Formats | PNG, JPEG, WebP | | File size | 40 MB at most | | Smallest image | 64 x 64 px | | Largest image | 64 megapixels | Width and height are read after the image's orientation is applied. [Rate and spend limits](/docs/api-reference/limits) has how many checks a client can send. ## What does the check answer? ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "input": { "sha256": "07c1858ff8d2c84e7ef521aa92e49ad251dec90553b65e600bd52ee84ef4065f", "width": 256, "height": 256, "format": "PNG", "has_alpha": true }, "verdicts": { "straight": { "repeat": "straight", "verdict": "bad", "edges": [ { "edge": "vertical", "verdict": "bad", "anomaly": 1.9222, "broken": [[90, 170]] }, { "edge": "horizontal", "verdict": "ok", "anomaly": 0.9328, "broken": [] } ] } }, "satisfied": [], "duplicate_edge": { "vertical": false, "horizontal": false }, "summary": "Does not tile seamlessly as any checked repeat type. Straight: bad (left/right edge, 1 stretch). Seams inside the tile are not checked." } ``` | Field | What it is | | - | - | | `input` | The image as read: `sha256` of the file bytes, `width` and `height` in px, `format` and `has_alpha`. | | `verdicts` | One [seam report](/docs/concepts#seam-report) per requested repeat type. | | `satisfied` | The requested repeat types the tile already passes, with the verdict `ok`. | | `duplicate_edge` | True for an edge where a 1 px line is repeated on both sides, which shows as a doubled line once tiled. | | `summary` | One plain-language line about the whole check. | Branch on `satisfied` and the verdicts, not on `summary`: the summary is for people and its wording may change. ## Which part of the tile does the check read? The tile's own edges, where copies of it meet: the right side against the left and the bottom against the top, at the offsets of the repeat type. It does not look for a seam inside the tile. ## Which errors can a check return? | Status | Code | Meaning | | - | - | - | | `413` | `payload_too_large` | The file is over 40 MB. | | `422` | `validation_error` | The `file` field is missing, or `repeats` names an unknown type. | | `422` | `unsupported_format` | The file is not a PNG, JPEG or WebP image. | | `422` | `decode_failed` | The file did not open as an image. | | `422` | `too_small` | The image is under 64 px on a side. | | `422` | `too_large` | The image is over 64 megapixels. | | `429` | `rate_limited` | Too many checks from this client. Wait the seconds in `Retry-After`. | A script that checks a folder sends the tiles one after another and waits as `Retry-After` says when it gets a `429`. [Errors](/docs/errors) has every code. # How repeat types, seam reports and fix credits work Source: https://makeseamless.com/docs/concepts What straight, half-drop and half-brick mean, how a seam report scores each edge, what a fix may change, and what a check and a fix cost in credits. Five ideas carry every answer the API gives: the repeat type, the seam report, the size guarantee, the changed area and the credit. ## What is a repeat type? A repeat type is how copies of a tile are laid next to each other. A tile can be seamless as one type and not as another, so every check and every fix names the type. | Type | How copies are laid out | Needs | | - | - | - | | `straight` | Side by side in rows and columns. | Any size. | | `half_drop` | Every other column drops by half the tile height. | An even height. | | `half_brick` | Every other row shifts right by half the tile width. | An even width. | A check reports a type that does not fit the size as `not_applicable`. A fix of such a type answers `422` `odd_dimension`. ## What is in a seam report? A seam report says, for one repeat type, whether the tile repeats cleanly and where it does not. It reads the tile's own edges, where copies of it meet: the right side against the left, the bottom against the top, at the offsets the repeat type uses. It does not look for a seam inside the tile, and the check's `summary` says so. * **`verdict`** for the type: `ok` (no seam), `minor` (a faint seam), `bad` (a visible seam) or `not_applicable`. * **`edges`**, one per edge. `vertical` is where the right side meets the left; `horizontal` is where the bottom meets the top. * **`anomaly`** per edge: how strong the seam is, on one scale for every edge. Higher is stronger. * **`broken`** per edge: the stretches where the seam shows, as half-open pixel ranges `[start, end)`. Along the vertical edge they are rows, along the horizontal edge columns. A check returns one report per requested type. A fix result carries two for its type: `before`, on your upload, and `after`, on the result. ## Does a fix change the size of my tile? Every image the API returns has the exact width and height of the image you uploaded, after its orientation is applied. It is a fresh lossless PNG that carries none of the upload's metadata. ## How do I see what a fix changed? A fix changes the tile only where it has to. Two masks, both the size of the tile, show where: * **The changed area** (`changed_mask_url`): white where the result's pixels may differ from the upload, black where they equal it. Usually a band along the edges. Its white share is `changed_share`. * **The visibly changed area** (`visible_changed_mask_url`): white where the result looks different from the upload. It lies inside the changed area, and its white share is `visible_changed_share`. The outcome `resampled` says that no version passed every quality check and that every pixel of the result changed. Its `changed_share` is 1, while `visible_changed_share` stays much smaller when most pixels changed only slightly. A resampled result passes the seam check, and fine detail may look a little softer. ## What do a check and a fix cost? A check is free. A fix costs credits, and the cost is known before it starts: `GET /v1/plans` returns it under `fix_credits`: `credits` for a fix, and `large_credits` for a tile above `large_above_px` on either side. * The credits are held when the job starts and come back when it fails, whatever its error code. * The same file with the same repeat type, fixed before, returns the stored result marked `cached`, at no charge. Sent again while that fix is still running, it returns the same job, at no charge. [Credits and plans](/docs/credits-and-plans) has the prices and the plans. # Connect an AI agent to check and fix pattern tiles Source: https://makeseamless.com/docs/connect-an-agent Give your coding agent a prompt, the skill file and a standing instruction, so it checks every tile for seams before upload and fixes only the ones that fail. Any agent that can send a web request can use MakeSeamless. The check needs no account and no key, so an agent can check every tile it makes. A fix uses an API key from your account and the same credits as the app. ## How do I get my agent to use MakeSeamless? Paste this into the agent you already use. It names the API and the rules an agent should follow, so it goes straight to the right calls. Check and fix pattern tiles with the MakeSeamless API. Use the MakeSeamless API at [https://makeseamless.com/v1](https://makeseamless.com/v1) to check my pattern tiles for seams, and fix the ones I ask for. * Read [https://makeseamless.com/docs/skill.md](https://makeseamless.com/docs/skill.md) first. * Check with `POST /v1/check`, the image as the multipart field `file`. It is free and needs no key. * Fix only when the repeat type I need is not in `satisfied`, and tell me the cost first (`GET /v1/plans`, `fix_credits`). * For a fix, send my key from the `MAKESEAMLESS_API_KEY` environment variable in the `x-api-key` header. Never ask me to paste the key here. * Poll `GET /v1/jobs/{id}`, waiting the seconds in `Retry-After`, until `status` is `succeeded` or `failed`, then download `result.image_url`. * Report the verdicts in `result.seam_report.before` and `.after`. Prompts that work well once the agent knows the API: * Check ./tile.png and tell me which repeat types it passes. * Check every PNG in ./papers as a straight repeat and write the file name, verdict and broken edge of each to seam-report.csv. * Check each tile in ./out as a half-drop repeat before you call it seamless. * Fix the tiles in ./papers that fail as straight, save each result as name-seamless.png, and tell me the credits used. ## How do I install the MakeSeamless skill? The [skill file](https://makeseamless.com/docs/skill.md) tells an agent what MakeSeamless does, what each call needs and what an account allows, so it gets the calls right the first time. Install the MakeSeamless skill into your agent. npx skills add [https://makeseamless.com/docs](https://makeseamless.com/docs) ## How do I make the check part of every task? An agent reads its project instructions at the start of every task, while it uses an installed skill only when it decides to. A line in those instructions makes the check a step the agent always takes. Paste this into the `AGENTS.md`, `CLAUDE.md` or rules file of the folder where you make or upload patterns. A standing instruction for AGENTS.md or CLAUDE.md. Pattern tiles: before you call a pattern tile seamless, upload it to a print-on-demand shop or print it, check it with MakeSeamless: `POST https://makeseamless.com/v1/check` with the image as the multipart field `file`. It is free and needs no key. Fix a tile with `POST /v1/fix` only when the repeat type I need is not in `satisfied`, and tell me the cost first. Read the key from `MAKESEAMLESS_API_KEY`. Docs: [https://makeseamless.com/docs/llms.txt](https://makeseamless.com/docs/llms.txt) [Why tiles show a line](/docs/knowledge-base/why-a-seamless-tile-shows-a-line-in-print) explains why a tile has to be checked each time it changes. ## Which rules should an agent follow? 1. Check first. It is free and needs no key. 2. Fix only when the repeat type the user needs is not in `satisfied`, and tell the user the cost first. 3. Follow the job until it succeeds or fails, waiting as `Retry-After` says. 4. Report the numbers in the result's seam report. A result already passed the seam check for its repeat type. 5. Never ask the user to paste an API key into a chat. Read it from the environment. On `402` `insufficient_credits`, the agent tells the user `error.details.credits_needed`; credits are bought by the user on makeseamless.com. ## Which files can an agent read? | File | What it is | | - | - | | [/llms.txt](https://makeseamless.com/llms.txt) | What MakeSeamless is and how an agent should use it, in one short file. | | [/docs/llms.txt](https://makeseamless.com/docs/llms.txt) | Every docs page as a list, with the agent instructions first. | | [/docs/llms-full.txt](https://makeseamless.com/docs/llms-full.txt) | Every docs page in one file. | | [/docs/skill.md](https://makeseamless.com/docs/skill.md) | The agent skill. | | [/docs/openapi.json](https://makeseamless.com/docs/openapi.json) | The whole `/v1` contract as one OpenAPI 3.1 file. | Every docs page also has a Markdown version: add `.md` to its address, or ask for `text/markdown`. ## Can an agent search these docs? The docs answer as an MCP server at `https://makeseamless.com/docs/mcp`, so an agent can search them instead of the web. The page menu at the top of each page adds it to Cursor and VS Code, or copies the install command for any other client. > [card] [Use an API key yourself](/docs/authentication) > > Write the calls from a backend or a script. > [card] [Errors](/docs/errors) > > Every code and which are worth a retry. # Credits, plans and prices: what a pattern seam fix costs Source: https://makeseamless.com/docs/credits-and-plans Checks are free. See what a fix costs in credits, what the Free and Pro plans give, the monthly credits and top-ups, and when a fix costs nothing. Checks are free. A fix costs credits, the same from the app, the API or an agent. Prices are in US dollars, and `GET /v1/plans` returns them live. ## How many credits does a fix cost? * A fix costs `fix_credits.credits` credits, and a tile above `fix_credits.large_above_px` on either side costs `fix_credits.large_credits`. [List plans](/docs/api-reference/account/retrieve-a-list-of-plans) returns the current numbers, so read the cost from there before a fix. * The credits are held when the job starts and come back when it fails, whatever its error code. * The same file with the same repeat type, fixed before, returns the stored result at no charge, with `cached` true. Sent again while its fix is still running, it returns the same job, at no charge. * A fix is paid from the account's credits. When there are not enough, `POST /v1/fix` answers `402` `insufficient_credits` with `details.credits_needed`. ## Which plans are there, and what do they cost? | Plan | Monthly | Billed yearly | What you get | | - | - | - | - | | Free | \$0 | | Unlimited checks | | Pro 50 | \$19 a month | \$15 a month, \$180 a year | 50 a month, or 600 a year | | Pro 135 | \$49 a month | \$39 a month, \$468 a year | 135 a month, or 1,620 a year | | Pro 280 | \$99 a month | \$79 a month, \$948 a year | 280 a month, or 3,360 a year | | Pro 430 | \$149 a month | \$119 a month, \$1,428 a year | 430 a month, or 5,160 a year | | Enterprise | Contact us | | Set with you | * **Free** has unlimited checks and gets no credits at sign-up. Fixes are paid with credits, which come with a Pro plan. * **Pro** grants its tier's credits for fixes. A yearly plan is billed once a year and grants twelve months of credits at each yearly payment. Each paid invoice adds the plan's allowance to the plan credits left, so unused plan credits carry over without a limit. Plan credits and top-up credits never expire and stay on the account after the plan ends. * **Top-ups** are for Pro accounts: 10 to 10,000 credits in one purchase, at the tier's price per credit: \$0.380, \$0.363, \$0.354 or \$0.347. * **Enterprise** credit volume and price are set with you. Write to [sales@makeseamless.com](mailto:sales@makeseamless.com). Fixes spend the plan credits first, then the sign-up credits, then the top-up credits. Subscribing, buying top-ups, changing the plan and cancelling are done signed in on [makeseamless.com](https://makeseamless.com/pricing), never with an API key. ## How do I see my credits from the API? `GET /v1/me` returns the account behind your key: `plan` (`free`, `pro` or `enterprise`), `tier`, `interval`, `subscription_status`, and `credits` by kind with their `total`. An agent reads it before a fix to tell the user what the fix costs and what is left. [Get account](/docs/api-reference/account/retrieve-the-current-account) runs it against your own key. # MakeSeamless API error codes and when to retry them Source: https://makeseamless.com/docs/errors Every stable error code with its status and meaning, which ones a retry can help, and a retry loop that waits as Retry-After says. Branch on error.code. Every failure has the same shape, on every endpoint. Branch on `code`, never on `message`: codes are stable, and messages are plain language that may change. Keep a default case that shows `message`, so a code you do not know yet degrades into a readable failure instead of a crash. ```json Error theme={"theme":{"light":"github-light","dark":"vesper"}} { "error": { "code": "insufficient_credits", "message": "Not enough credits for this fix.", "retryable": false, "details": { "credits_needed": 1 } } } ``` | Field | What it is | | - | - | | `code` | The stable code. The tables below list every one. | | `message` | A plain-language explanation for people. Do not parse it. | | `retryable` | True when the same request can succeed later, unchanged. | | `details` | Optional machine-readable context, such as `credits_needed` or `pattern_tile`. | A `429` and a `409` `result_not_ready` carry `Retry-After`, the seconds to wait before you try again. ## Which status codes can the API answer? | Status | Meaning | | - | - | | `200` | The request succeeded. | | `201` | The rating was stored. | | `202` | The fix was accepted; poll the job. | | `401` | The API key is missing, revoked or unknown. | | `402` | Not enough credits for the fix. | | `404` | No such job or image, or the image is no longer stored. | | `409` | The job has no image yet, or none at all, or its rating window closed. | | `413` | The file is larger than 40 MB. | | `422` | The request or the image cannot be used. | | `429` | A limit was reached. Wait the seconds in `Retry-After`. | | `500` | Something went wrong on our side. | ## Which errors mean the image cannot be used? | Status | Code | Retry | What to do | | - | - | - | - | | `413` | `payload_too_large` | no | The file is over 40 MB. Send a smaller file. | | `422` | `validation_error` | no | A field is missing or has a value the API does not know. | | `422` | `unsupported_format` | no | Send a PNG, JPEG or WebP image. | | `422` | `decode_failed` | no | The file did not open as an image. Save it again, for example as PNG. | | `422` | `too_small` | no | The image is under 64 px on a side. | | `422` | `too_large` | no | A check takes at most 64 megapixels, a fix at most 3600 px on a side. `details` may say the limit. | | `422` | `odd_dimension` | no | Fix only: half-drop needs an even height and half-brick an even width. Choose another repeat type or resize the tile. | | `422` | `transparent_seam_band` | no | Fix only: the tile has transparent pixels near its edges. Fill them, then send the tile again. | ## Which errors are about the key or the credits? | Status | Code | Retry | What to do | | - | - | - | - | | `401` | `auth_required` | no | Send a valid key in the `x-api-key` header. The key may be missing, revoked or unknown. | | `402` | `insufficient_credits` | no | Tell the user `details.credits_needed`. Credits come with a Pro plan on makeseamless.com, and a Pro account can buy top-ups. | ## Which errors mean a limit was reached? | Status | Code | Retry | What to do | | - | - | - | - | | `429` | `rate_limited` | yes | Too many checks from this client. Wait `Retry-After` seconds. | | `429` | `too_many_running_fixes` | yes | The account already has 3 fixes queued or running. Wait for one to finish. | | `429` | `daily_capacity_reached` | yes | Fixes are at capacity for now. Try again after `Retry-After` seconds. | [Rate and spend limits](/docs/api-reference/limits) has the numbers behind them. ## Which errors come from jobs and their images? | Status | Code | Retry | What to do | | - | - | - | - | | `404` | `not_found` | no | No job with this id, no image with this id in the job, or an image older than 30 days. | | `409` | `result_not_ready` | yes | The job is still `queued` or `running`. Poll it again after `Retry-After`. | | `409` | `no_result` | no | The job failed, so it has no image. | | `409` | `rating_closed` | no | Ratings close 30 days after the job was created. | ## Why did a fix job fail? A job that ends in `failed` carries its own `error`, in the same shape. | Code | Retry | What happened | | - | - | - | | `no_seamless_candidate` | no | No version of the tile passed the seam check. No image and no charge. | | `no_seamless_candidate` with `details.pattern_tile` false | no | Rare: the image does not read as a repeating pattern tile, and no version passed the seam check. No image and no charge. Send a single repeating tile instead. | | `service_unavailable` | yes | The fix could not run for now. No charge; start it again later. | | `interrupted` | yes | The service stopped while the job ran. No charge; start the fix again. | | `internal_error` | no | Something went wrong on our side. No image and no charge. | ## Which error can any request return? | Status | Code | Retry | What to do | | - | - | - | - | | `500` | `internal_error` | no | Something went wrong on our side. Try again later, and write to [hello@makeseamless.com](mailto:hello@makeseamless.com) if it stays. | ## Which errors come from billing on makeseamless.com? Three codes come only from the billing pages of makeseamless.com, which are used signed in and never with an API key. They are listed so the set is complete. | Status | Code | Meaning | | - | - | - | | `403` | `pro_required` | Top-up credits are for Pro plans. | | `409` | `already_subscribed` | The account already has a subscription. | | `409` | `no_subscription` | The account has no subscription to change. | ## When should I retry a request? Retry only when `retryable` is true. Wait as `Retry-After` says when it is present, and otherwise start at 1 second and double the wait each time. A fix that failed with `service_unavailable` or `interrupted` cost nothing, so a new `POST /v1/fix` is safe. An answer with no JSON body, such as a `502`, `503` or `504` from the network, can be retried the same way. ```python Python theme={"theme":{"light":"github-light","dark":"vesper"}} import time import requests def call(send, attempts=5): wait = 1 for _ in range(attempts): r = send() if r.ok: return r try: error = r.json()["error"] except (ValueError, KeyError): error = {"retryable": r.status_code >= 502} if not error.get("retryable"): r.raise_for_status() time.sleep(int(r.headers.get("Retry-After", wait))) wait *= 2 r.raise_for_status() ``` ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} async function call(send, attempts = 5) { let wait = 1; let res; for (let i = 0; i < attempts; i++) { res = await send(); if (res.ok) return res; const body = await res.clone().json().catch(() => null); const retryable = body?.error?.retryable ?? res.status >= 502; if (!retryable) break; const after = Number(res.headers.get("Retry-After") ?? wait); await new Promise((done) => setTimeout(done, after * 1000)); wait *= 2; } throw new Error(`${res.status}`); } ``` # Fix the seam of a pattern tile at the same pixel size Source: https://makeseamless.com/docs/fix-and-jobs Start a fix for one repeat type, poll the job and download a PNG that has passed the seam check. A fix with no passing result costs nothing. `POST /v1/fix` validates the image, starts a job that makes it repeat seamlessly for the repeat type you choose, and answers `202` with the job. You follow the job with `GET /v1/jobs/{job_id}` and download the result, a PNG with the exact width and height of your upload. ## How do I start a fix? A fix needs an [API key](/docs/authentication) and costs credits. | Field | Required | What it is | | - | - | - | | `file` | yes | The image: PNG, JPEG or WebP. | | `repeat` | yes | `straight`, `half_drop` or `half_brick`. | ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} import { readFile } from "node:fs/promises"; const form = new FormData(); form.append("file", new Blob([await readFile("tile.png")]), "tile.png"); form.append("repeat", "straight"); const response = await fetch("https://makeseamless.com/v1/fix", { method: "POST", headers: { "x-api-key": "smls_your_api_key" }, body: form, }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} new CURLFile("tile.png"), "repeat" => "straight", ]); $response = curl_exec($ch); curl_close($ch); echo $response; ``` ```python Python theme={"theme":{"light":"github-light","dark":"vesper"}} import requests with open("tile.png", "rb") as file: response = requests.post( "https://makeseamless.com/v1/fix", headers={"x-api-key": "smls_your_api_key"}, files={"file": file}, data={"repeat": "straight"}, ) response.raise_for_status() print(response.json()) ``` ```ruby Ruby theme={"theme":{"light":"github-light","dark":"vesper"}} require "net/http" require "uri" uri = URI("https://makeseamless.com/v1/fix") request = Net::HTTP::Post.new(uri) request["x-api-key"] = "smls_your_api_key" request.set_form( [ ["file", File.open("tile.png")], ["repeat", "straight"], ], "multipart/form-data", ) response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts response.body ``` ```go Go theme={"theme":{"light":"github-light","dark":"vesper"}} package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { var payload bytes.Buffer form := multipart.NewWriter(&payload) file, err := os.Open("tile.png") if err != nil { panic(err) } defer file.Close() part, err := form.CreateFormFile("file", "tile.png") if err != nil { panic(err) } if _, err := io.Copy(part, file); err != nil { panic(err) } form.WriteField("repeat", "straight") form.Close() req, err := http.NewRequest("POST", "https://makeseamless.com/v1/fix", &payload) if err != nil { panic(err) } req.Header.Set("x-api-key", "smls_your_api_key") req.Header.Set("Content-Type", form.FormDataContentType()) res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(string(body)) } ``` ```java Java theme={"theme":{"light":"github-light","dark":"vesper"}} import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; String boundary = "makeseamless-form"; List parts = new ArrayList<>(); parts.add(("--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"tile.png\"\r\n\r\n") .getBytes(StandardCharsets.UTF_8)); parts.add(Files.readAllBytes(Path.of("tile.png"))); parts.add("\r\n".getBytes(StandardCharsets.UTF_8)); parts.add(("--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"repeat\"\r\n\r\n" + "straight\r\n").getBytes(StandardCharsets.UTF_8)); parts.add(("--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8)); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://makeseamless.com/v1/fix")) .header("x-api-key", "smls_your_api_key") .header("Content-Type", "multipart/form-data; boundary=" + boundary) .POST(HttpRequest.BodyPublishers.ofByteArrays(parts)) .build(); HttpResponse response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Post, "https://makeseamless.com/v1/fix"); request.Headers.Add("x-api-key", "smls_your_api_key"); var form = new MultipartFormDataContent(); form.Add(new ByteArrayContent(File.ReadAllBytes("tile.png")), "file", "tile.png"); form.Add(new StringContent("straight"), "repeat"); request.Content = form; var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST "https://makeseamless.com/v1/fix" \ -H "x-api-key: smls_your_api_key" \ -F "file=@tile.png" \ -F "repeat=straight" ``` | | Limit | | - | - | | File size | 40 MB at most | | Size | from 64 x 64 px to 3600 px on a side | | `half_drop` | an even height | | `half_brick` | an even width | | Edges | no transparent pixels near the edges | A file that breaks a limit is refused at once with a `422` and costs nothing. An account can have 3 fixes queued or running at a time; a fourth answers `429` `too_many_running_fixes` with `Retry-After`. The `202` carries the job, `Location: /v1/jobs/{job_id}` and `Retry-After`. The same file with the same repeat type, fixed before, gets a job that has already `succeeded`, with `cached` true, at no charge. Sent again while its fix is still `queued` or `running`, it gets that same job back, at no charge. ## What does a fix job look like? ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "id": "job_2432c1827f32450f800694e552e9915c", "status": "queued", "stage": "queued", "repeat": "straight", "cached": false, "input": { "sha256": "9cf95d486b4c7d1bd6022a3e51d7c89b89ba5239d0378033c9a9f53413dd7770", "width": 256, "height": 256, "format": "PNG", "has_alpha": true }, "created_at": "2026-11-03T15:24:08.435Z", "started_at": null, "finished_at": null, "result": null, "error": null, "candidates": [] } ``` | Field | What it is | | - | - | | `status` | `queued`, `running`, `succeeded` or `failed`. | | `stage` | Progress for display, only ever forward. See [Stages](#stages). | | `cached` | True when the result was made for the same file and repeat type before and served again, at no charge. | | `result` | Present when `status` is `succeeded`. | | `error` | Present when `status` is `failed`: `code`, `message` and `retryable`. | | `candidates` | The other images of a succeeded job. Empty until then. | ## How do I know when a fix is done? `GET /v1/jobs/{job_id}` returns the job as it stands and needs no key. While it is `queued` or `running`, the answer carries `Retry-After`: wait that many seconds, then ask again, until `status` is `succeeded` or `failed`. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} const response = await fetch("https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c", { method: "GET", }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c"); var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c" ``` Anyone with a job's id can read the job and its images, so keep the ids to yourself. [The whole loop](/docs/quickstart#the-whole-loop) shows polling in Python and Node.js. ### What does each job stage mean? | `stage` | What is happening | | - | - | | `queued` | Waiting to start. | | `fixing` | Seamless versions of the tile are being made. | | `finishing` | The result and its files are being prepared. | | `finished` | The job succeeded or failed. | ## What comes with a fixed tile? ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}} { "outcome": "ok", "image_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/result", "changed_mask_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/changed_mask", "visible_changed_mask_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/visible_mask", "width": 256, "height": 256, "sha256": "d896bd338713c58bfc87b1c235410a4e663a37dcc67e6b5b9ea7197ce7905ee8", "resampled": false, "changed_share": 0.3017578125, "visible_changed_share": 0.21240234375, "seam_report": { "before": { "repeat": "straight", "verdict": "bad", "edges": [] }, "after": { "repeat": "straight", "verdict": "ok", "edges": [] } }, "alternative": { "id": "candidate_a", "image_url": "/v1/jobs/job_2432c1827f32450f800694e552e9915c/candidates/candidate_a", "passed": false, "changed_share": 1.0 } } ``` | Field | What it is | | - | - | | `outcome` | `ok`: the result passed the seam check and the quality checks that apply to the image. `resampled`: every pixel changed; it passes the seam check, and fine detail may look a little softer. | | `image_url` | The fixed tile. Always the upload's width and height. | | `changed_mask_url`, `changed_share` | The [changed area](/docs/concepts#visible-change) and its share of the tile. | | `visible_changed_mask_url`, `visible_changed_share` | The visibly changed area and its share. Absent on results made before they were offered. | | `seam_report` | The [seam report](/docs/concepts#seam-report) of the repeat type on the upload (`before`) and on the result (`after`). | | `alternative` | The other image shown next to the result, or null. Offer its download only when `passed` is true. | A result always passed the seam check on its tile edges for the job's repeat type. A fix that cannot reach that gives no image: the job fails with `no_seamless_candidate` and costs nothing. ## How do I download the fixed tile and its masks? | Path | What it is | | - | - | | `GET /v1/jobs/{job_id}/result` | The fixed tile. | | `GET /v1/jobs/{job_id}/changed_mask` | The changed area. | | `GET /v1/jobs/{job_id}/visible_mask` | The visibly changed area; `404` on a result without one. | | `GET /v1/jobs/{job_id}/candidates/{candidate_id}` | Another image of the job, by its `id` in `candidates`. | Each is a fresh lossless PNG with the upload's exact width and height and none of its metadata. Before the job succeeds they answer `409` `result_not_ready`; for a failed job, `409` `no_result`. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} import { writeFile } from "node:fs/promises"; const response = await fetch("https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/result", { method: "GET", }); if (!response.ok) throw new Error(await response.text()); await writeFile("result.png", Buffer.from(await response.arrayBuffer())); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofByteArray()); if (response.statusCode() != 200) { throw new RuntimeException(new String(response.body())); } Files.write(Path.of("result.png"), response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; var request = new HttpRequestMessage(HttpMethod.Get, "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/result"); var client = new HttpClient(); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); File.WriteAllBytes("result.png", await response.Content.ReadAsByteArrayAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X GET "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/result" \ -o result.png ``` Images are kept for 30 days. After that their addresses answer `404` `not_found`, so download what you need. ## What are the other images of a job? A succeeded job lists in `candidates` the other images the fix made besides the result, `candidate_a` first, which is the one in `result.alternative`. Each has `passed` (true when it passed the same checks as a result), `changed_share` and `resampled`. Ids are per job. Every image with `passed` true is another result of the same fix, at no extra charge: the app shows it as Result 2, 3 and so on, with its own download. An image with `passed` false did not pass, and the app does not show it. ## What happens when a fix fails? | Code | Retry? | What happened | | - | - | - | | `no_seamless_candidate` | no | No version of the tile passed the seam check. No image, no charge. | | `no_seamless_candidate` with `details.pattern_tile` false | no | Rare: the image does not read as a repeating pattern tile, and no version passed the seam check. No image, no charge. Send a single repeating tile instead. | | `service_unavailable` | yes | The fix could not run for now. No charge; start it again later. | | `interrupted` | yes | The service stopped while the job ran. No charge; start the fix again. | | `internal_error` | no | Something went wrong on our side. No image, no charge. | ## How do I rate a fixed tile? `POST /v1/jobs/{job_id}/rating` stores your rating of the result or of another image of the job: `seamless` or `seam_visible`. Ratings help us make better fixes. A new rating of the same image replaces the last one. ```js Node.js theme={"theme":{"light":"github-light","dark":"vesper"}} const response = await fetch("https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/rating", { method: "POST", headers: { "x-api-key": "smls_your_api_key", "Content-Type": "application/json", }, body: JSON.stringify({ "rating": "seamless", "candidate": "result" }), }); const data = await response.json(); console.log(data); ``` ```php PHP theme={"theme":{"light":"github-light","dark":"vesper"}} response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ```csharp .NET theme={"theme":{"light":"github-light","dark":"vesper"}} using System.Net.Http; using System.Text; var payload = """ { "rating": "seamless", "candidate": "result" } """; var request = new HttpRequestMessage(HttpMethod.Post, "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/rating"); request.Headers.Add("x-api-key", "smls_your_api_key"); request.Content = new StringContent(payload, Encoding.UTF8, "application/json"); var client = new HttpClient(); var response = await client.SendAsync(request); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST "https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/rating" \ -H "x-api-key: smls_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "rating": "seamless", "candidate": "result" }' ``` `candidate` is `result` for the fixed tile, or the `id` of another image. Only the account that started the job, or was served it as a cached job, can rate it, and only within 30 days of the job's creation. After that the answer is `409` `rating_closed`. # MakeSeamless API: check and fix seamless pattern tiles Source: https://makeseamless.com/docs/index Check whether a pattern tile is seamless for free, with no key, and fix its seam at the same pixel size. Every fix you download has passed the seam check. MakeSeamless (makeseamless.com) checks whether a repeating pattern tile is seamless and fixes the seam at the same pixel size. The check is free and needs no account, every fix you download passes the seam check for the repeat you chose, and a fix with no passing result costs nothing. ## Why check a tile before it prints? A tile can look finished on its own and still show a line where its copies meet: a faint white line, a grid of pixel lines, edges a few pixels off. The line often appears only once the pattern repeats at print size, on the proof or on the product, after the sample or the order is paid for. Fixing it by hand is trial and error, and the same steps do not promise the same result. [Why tiles show a line](/docs/knowledge-base/why-a-seamless-tile-shows-a-line-in-print) explains where seams come from and what a verified result means. ## What does the API do? Send a tile and learn, for each repeat type (straight, half-drop and half-brick), whether it tiles without a seam and where its edges break. When it does not, start a fix and get the same tile back at the exact same pixel size, repeating cleanly for the repeat type you chose. The result carries the seam report before and after and a mask of what changed; outside that area, every pixel is your original. ## What do I need to start? 1. A tile: a PNG, JPEG or WebP image 2. For a fix, [an API key](/docs/authentication) from your account The check needs neither an account nor a key. The [quickstart](/docs/quickstart) runs a check, then a fix, one request at a time. ## Where does the check fit in my workflow? Check each tile where a seam usually slips in, before any money is spent: after you make or generate it, after you resize or export it for print, and before you upload it to a print-on-demand shop or order a sample. Fix only the repeat types you sell in that fail. An agent can run the same check on every tile it makes; [Connect an agent](/docs/connect-an-agent) shows how. ## Explore > [card] [Check a tile](/docs/check) > > A verdict per repeat type, free and at once. > [card] [Fix a tile](/docs/fix-and-jobs) > > A job that returns the tile repeating cleanly. > [card] [What a fix costs](/docs/credits-and-plans) > > Credits, plans and top-ups. > [card] [Connect an agent](/docs/connect-an-agent) > > A prompt, a skill file and the machine files. ## Reference > [card] [API introduction](/docs/api-reference/introduction) > > Base URL, authentication, answers, versioning. > [card] [Errors](/docs/errors) > > Every stable code and when a retry helps. # Why a seamless pattern tile still shows a line in print Source: https://makeseamless.com/docs/knowledge-base/why-a-seamless-tile-shows-a-line-in-print A tile can look finished and still show a white line where its copies meet. See why seams are hard to catch and fix, and what a verified result means. A pattern tile is seamless only when its edges run into each other at every join: the right side into the left, the bottom into the top, and, for a half-drop or half-brick repeat, at the half offsets as well. A tile can look finished on its own and still break at one of those joins. The break shows only once copies of the tile meet, and often only at print size, on the proof or on the product. MakeSeamless (makeseamless.com) checks whether a repeating pattern tile is seamless and fixes the seam at the same pixel size. The check is free and needs no account, every fix you download passes the seam check for the repeat you chose, and a fix with no passing result costs nothing. ## Why does a tile that looks finished show a line once it repeats? One tile on its own tells you little about its joins. The flaws that matter sit on the outer edges, where the eye does not look: * A row or column of edge pixels that is lighter, darker or transparent. It repeats as a faint white line, or as a grid of thin lines across the whole print. * Edges that are a few pixels off, so a motif breaks where two copies meet. * The same line of pixels on both sides of a join, which shows as a doubled line once the tile repeats. * Edges that meet as a straight repeat but not at the half offset of a half-drop or half-brick repeat, or the other way around. Sellers call it a white line, pixel lines, a ghost line or a visible repeat. Spoonflower's help center calls them pixel lines: "a design issue we see often at Spoonflower." It adds that such flaws "are sometimes hard to see but will print as lightened or unprinted areas, solid colored lines". A seller on r/midjourney put it plainly: "When I put the pattern on a product, there is a faint white line seam." A tile that passed can still break later. Resizing it to print size, exporting it again or saving it in another format can open a seam that was not there before. Midjourney's own documentation says that upscaling a tile "will often break the seamless repeating pattern", while a print file needs far more pixels than a generated tile usually has. A seller on r/EtsyDigitalSeller: "AI outputs are usually too low-res for high-quality printing." ## Why is a seamless tile hard and costly to get right? **The seam hides until money is spent.** Spoonflower prints the file as it was uploaded and recommends physical samples before you sell: "there will never be a substitute for sampling your designs on physical products". A seam found on a sample, a fabric order or a customer's product is found after the order is paid for. One Spoonflower customer found a flaw in the file had "caused horizontal and verticle pixel lines throughout my order." **Checking by hand is slow.** The usual routine is to tile the file two by two or three by three and zoom in on every join, once for each repeat you sell in. Printify's guide for all-over prints tells sellers to "spot-test it for hidden seams" before they commit a tile, and says: "Sellers burn hours nudging elements by hand in Illustrator or Photoshop, chasing invisible gaps." **Tools disagree about the same tile.** A tile can pass one online checker and still show a line in a print-on-demand preview, or show a line in one editor and none in another, so a real seam is hard to tell from a display glitch. After two tools gave two answers, one seller wrote: "I don't know who to trust now." **A seam on a live listing costs twice.** A Redbubble seller: "I've rechecked and reuploaded so many times and I'm tired of remaking the listing." A seller of pattern files on Etsy: "I had a client ask for a nominal refund claiming our seamless patterns weren’t aligning." ## Why do the same steps not give the same result? Making a tile seamless by hand or with an image model is trial and error, and the same steps do not promise the same result: * One sportswear seller on r/AdobeIllustrator: "I've got an issue with about 50% of my seamless patterns created in Illustrator." * Another, exporting the same pattern, found Illustrator "sometimes exports the images with a line and sometimes it doesn't". * A ChatGPT image user trying for seamless tiles: "it's always a few pixels off." * Printify's guide to tiles made with Midjourney: "This is the mistake that ruins most tile attempts: do not upscale." So the only way to know a tile repeats cleanly is to check the file you will actually print, each time it changes. ## What does a verified MakeSeamless result mean? * **It passed the seam check.** The result of a fix has always passed the seam check on its tile edges for the repeat type you chose. Its seam report before and after, the same report a [check](/docs/check) returns, gives the verdict and the strength of each edge on your upload and on the result. * **No pass, no charge.** When no version of the tile passes, the fix gives no image and fails with `no_seamless_candidate`, and the credits held for it come back. A result that fails the seam check is never handed back as the result. * **The same size.** The result has the exact width and height of your upload, as a lossless PNG. * **Your pixels stay yours.** Outside the changed area every pixel is your original, and a mask shows where that area is. The outcome `resampled` says that no version passed every quality check and that every pixel of the result changed. * **What the check reads.** The joins where copies of the tile meet, for each repeat type. It does not look for a seam inside the tile. [Fix a tile](/docs/fix-and-jobs#result) lists every field of a result, and [Concepts](/docs/concepts#visible-change) explains the changed area. ## Where does the check fit in a seller's workflow? Check a tile at the points where a seam slips in, before any money is spent: 1. After you make or generate the tile. 2. After you resize, upscale or export it for print. 3. Before you upload it to a print-on-demand shop, order a sample or sell the file. Fix only the repeat types you sell in that fail. The check is one request and needs no key, so an agent can check every tile it makes or moves: [Connect an agent](/docs/connect-an-agent) has a prompt and a standing instruction to paste. ## Sources Read on 4 November 2026. * Spoonflower help center, [Checking for repeat flaws in designs](https://support.spoonflower.com/hc/en-us/articles/205483316-Checking-for-Repeat-Flaws-in-Designs) * Spoonflower help center, [Proofing your design](https://support.spoonflower.com/hc/en-us/articles/205308430-Proofing-Your-Design) * Midjourney documentation, [Tile](https://docs.midjourney.com/hc/en-us/articles/32197978340109-Tile) * Printify, [Turn one AOP tile into a full store](https://printify.com/knowledge-hub/turn-one-aop-tile-into-a-full-store/) * Printify, [Midjourney v6 parameters for floral prints](https://printify.com/knowledge-hub/midjourney-v6-parameters-for-floral-prints/) * Sellers on Reddit: [r/midjourney](https://www.reddit.com/r/midjourney/comments/161ctcf/seamless_pattern_tile_issues_faint_white_line/), [r/midjourney](https://www.reddit.com/r/midjourney/comments/12ecne1/tile_tiles_with_faded_white_line_seams/), [r/EtsyDigitalSeller](https://www.reddit.com/r/EtsyDigitalSeller/comments/1uxzif4/comment/oy571je/), [r/GIMP](https://www.reddit.com/r/GIMP/comments/111m2ty/help_please_removing_pixel_lines_on_jpg_image/), [r/redbubble](https://www.reddit.com/r/redbubble/comments/z9fma5/there_is_a_white_line_in_my_pattern_that_only/), [r/EtsySellers](https://www.reddit.com/r/EtsySellers/comments/1g9txz4/do_buyers_ever_just_demand_refund_in_order_to/), [r/AdobeIllustrator](https://www.reddit.com/r/AdobeIllustrator/comments/17oz07v/the_issue_with_the_notsoseamless_patterns/), [r/AdobeIllustrator](https://www.reddit.com/r/AdobeIllustrator/comments/11wskkq/comment/jd0it60/), [r/ChatGPTPro](https://www.reddit.com/r/ChatGPTPro/comments/194187w/seamless_patterns_in_chatgpt_pro/) # Quickstart: check a pattern tile, then fix its seam Source: https://makeseamless.com/docs/quickstart Check a pattern tile for seams in one request with no key, then fix it with an API key and download the PNG at its exact pixel size. Copy each call. Use this pre-built prompt to get an agent calling the MakeSeamless API correctly. # Check and fix a pattern tile with the MakeSeamless API **Purpose:** Enforce only the current and correct instructions for checking a repeating pattern tile for seams and fixing it with the [MakeSeamless](https://makeseamless.com/) API over HTTP. **Scope:** All AI-generated advice or code that calls MakeSeamless must follow these guardrails. *** ## **1. Official setup** The base URL is `https://makeseamless.com/v1`. The check needs no account and no key. A fix needs an API key from the user's account, created at [https://makeseamless.com/app/account](https://makeseamless.com/app/account) and stored in an environment variable called `MAKESEAMLESS_API_KEY`. Keys begin with `smls_` and travel in the `x-api-key` header, the only header the API reads for authentication. ### **Check the tile** ```bash theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST https://makeseamless.com/v1/check \ -F "file=@tile.png" ``` The repeat types the tile already passes are in `satisfied`. ### **Start a fix** ```bash theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST https://makeseamless.com/v1/fix \ -H "x-api-key: $MAKESEAMLESS_API_KEY" \ -F "file=@tile.png" \ -F "repeat=straight" ``` The answer is `202` with a job. Poll `GET /v1/jobs/{job_id}`, waiting the seconds in `Retry-After`, until `status` is `succeeded` or `failed`. Download `https://makeseamless.com` plus `result.image_url`. *** ## **2. Request body reference** `POST /v1/check` takes a multipart form: `file` (binary, required, a PNG, JPEG or WebP image) and `repeats` (optional, `straight`, `half_drop` or `half_brick`, sent once per type; all three when left out). `POST /v1/fix` takes a multipart form: `file` (binary, required) and `repeat` (required, `straight`, `half_drop` or `half_brick`). Half-drop needs an even height and half-brick an even width. `POST /v1/jobs/{job_id}/rating` takes JSON: `rating` (`seamless` or `seam_visible`) and `candidate` (`result`, or the `id` of another image of the job). An image is at most 40 MB and at least 64 x 64 px. A check takes up to 64 megapixels; a fix takes up to 3600 px on a side and no transparent pixels near the edges. *** ## **3. Critical instructions for AI models** ### **3.1 ALWAYS DO THE FOLLOWING** 1. **Check first.** It is free and needs no key. 2. **Tell the user what the fix costs** (`GET /v1/plans`, `fix_credits`) before starting it. 3. **Send the key in the `x-api-key` header**, read from the environment. 4. **Wait the seconds in `Retry-After`** between two polls of a job. 5. **Branch on `error.code`**, with a default case that shows `error.message`. ### **3.2 NEVER DO THE FOLLOWING** 1. **Do not** ask the user to paste a key into the conversation, and do not put an `smls_` key in code that ships to a browser. 2. **Do not** fix a tile whose repeat type is already in `satisfied`. 3. **Do not** retry a request unless `error.retryable` is true. 4. **Do not** invent a field name. If it is not in the documentation, it does not exist. On `402` `insufficient_credits`, tell the user `error.details.credits_needed`; credits are bought by the user on makeseamless.com. Verify every rule above before returning any MakeSeamless solution. If a check **fails**, **stop** and revise until compliance is achieved. Every error code and its retry rule: [https://makeseamless.com/docs/errors](https://makeseamless.com/docs/errors) For the entire docs for MakeSeamless, see [https://makeseamless.com/docs/llms-full.txt](https://makeseamless.com/docs/llms-full.txt) ## What do I need before I start? Before you start, you'll need: * A pattern tile: a PNG, JPEG or WebP image of at least 64 x 64 px * For the fix, a MakeSeamless [API key](/docs/authentication) ## Guide **1. Check the tile** Send the image as the multipart field `file`. No account and no key. ```bash theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST https://makeseamless.com/v1/check \ -F "file=@tile.png" ``` The answer holds a verdict for each repeat type, and `satisfied` lists the ones the tile already passes. If yours is there, you are done. [Check a tile](/docs/check) explains every field. **2. Create an API key** Ask the user to create a key at [https://makeseamless.com/app/account](https://makeseamless.com/app/account) and to set it as `MAKESEAMLESS_API_KEY`. Never ask for the key in the conversation. **3. Start a fix** Send the same file and the repeat type you need: `straight`, `half_drop` or `half_brick`. ```bash theme={"theme":{"light":"github-light","dark":"vesper"}} curl -X POST https://makeseamless.com/v1/fix \ -H "x-api-key: $MAKESEAMLESS_API_KEY" \ -F "file=@tile.png" \ -F "repeat=straight" ``` The answer is `202` with a job in `queued`. Keep its `id`. The same file with the same repeat type, fixed before, comes back as a job that has already `succeeded`, at no charge. **4. Follow the job** Ask for the job until `status` is `succeeded` or `failed`, waiting the seconds that `Retry-After` gives between two calls. Reading a job needs no key. ```bash theme={"theme":{"light":"github-light","dark":"vesper"}} curl https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c ``` **5. Download the result** A succeeded job has the fixed PNG at `result.image_url`, with the exact width and height of your upload. ```bash theme={"theme":{"light":"github-light","dark":"vesper"}} curl -o tile-seamless.png \ https://makeseamless.com/v1/jobs/job_2432c1827f32450f800694e552e9915c/result ``` A failed job carries `error.code` instead. A failure costs nothing, whatever its code. ## The whole loop The same steps in one script. ```python Python expandable theme={"theme":{"light":"github-light","dark":"vesper"}} import os import time import requests BASE = "https://makeseamless.com" KEY = os.environ["MAKESEAMLESS_API_KEY"] with open("tile.png", "rb") as f: tile = f.read() check = requests.post(f"{BASE}/v1/check", files={"file": tile}).json() if "straight" in check["satisfied"]: raise SystemExit("Already seamless as straight.") r = requests.post( f"{BASE}/v1/fix", headers={"x-api-key": KEY}, files={"file": tile}, data={"repeat": "straight"}, ) if r.status_code != 202: raise SystemExit(r.json()["error"]["code"]) job = r.json() while job["status"] in ("queued", "running"): time.sleep(int(r.headers.get("Retry-After", "1"))) r = requests.get(f"{BASE}/v1/jobs/{job['id']}") job = r.json() if job["status"] == "failed": raise SystemExit(job["error"]["code"]) image = requests.get(BASE + job["result"]["image_url"]) with open("tile-seamless.png", "wb") as out: out.write(image.content) ``` ```js Node.js expandable theme={"theme":{"light":"github-light","dark":"vesper"}} import { readFile, writeFile } from "node:fs/promises"; const BASE = "https://makeseamless.com"; const KEY = process.env.MAKESEAMLESS_API_KEY; const tile = new Blob([await readFile("tile.png")]); const checkForm = new FormData(); checkForm.append("file", tile, "tile.png"); const check = await ( await fetch(`${BASE}/v1/check`, { method: "POST", body: checkForm }) ).json(); if (check.satisfied.includes("straight")) { throw new Error("Already seamless as straight."); } const fixForm = new FormData(); fixForm.append("file", tile, "tile.png"); fixForm.append("repeat", "straight"); let res = await fetch(`${BASE}/v1/fix`, { method: "POST", headers: { "x-api-key": KEY }, body: fixForm, }); if (res.status !== 202) throw new Error((await res.json()).error.code); let job = await res.json(); while (job.status === "queued" || job.status === "running") { const wait = Number(res.headers.get("Retry-After") ?? 1); await new Promise((done) => setTimeout(done, wait * 1000)); res = await fetch(`${BASE}/v1/jobs/${job.id}`); job = await res.json(); } if (job.status === "failed") throw new Error(job.error.code); const image = await fetch(BASE + job.result.image_url); const png = Buffer.from(await image.arrayBuffer()); await writeFile("tile-seamless.png", png); ``` ## What does a fix cost? A fix costs credits, which come with a Pro plan; the check is free. [Credits and plans](/docs/credits-and-plans) has the numbers. ## Next steps > [card] [The seam report](/docs/concepts) > > Repeat types, verdicts and visible change. > [card] [Fix and jobs](/docs/fix-and-jobs) > > Stages, results, other images, ratings. > [card] [Errors](/docs/errors) > > Every code and when a retry helps. > [card] [Connect an agent](/docs/connect-an-agent) > > Let an agent run these calls for you.