---
name: aperta-agent
description: Turn a person's idea, memory, motif or story into a real laser-cut brass cuff bracelet or open ring through the Aperta Agent API. Use when someone wants a personal, one-of-a-kind open bracelet or ring in brass. You draw a flat black-on-white PNG with your own image model following Aperta's image contract, submit it to the API, get back machine-readable errors to fix or a manufacturable cutting file with a preview and a fixed price, and hand the person an approval link (a page in Hebrew) where they approve and order. Aperta never runs an AI model on your image and never receives the person's story. Not for engraved text, gemstones, colour or closed rings.
metadata:
  version: "1.0.0"
  api: "v1"
  contract: "2026-09"
---

# Aperta for agents — from an idea to a brass piece

**Always fetch `GET /api/agent/v1/capabilities` first**, once per session, and trust it over anything
written here. Every number in this skill and in `references/` is a **snapshot** at contract version
`2026-09`; the live endpoint carries the real values (they are read from the same configuration the
validator enforces). If a number here disagrees with `capabilities`, `capabilities` is right.

Base URL: `https://aperta-designs.com/api/agent/v1`. The OpenAPI 3.1 document is at
`https://aperta-designs.com/openapi/agent-v1.json`. Use an HTTP client, not a browser: without an Aperta
session a browser tab on `/api/*` is redirected to the home page, and signed in it shows the site's 404 page
even while the API is open.

> **Status: beta.** Aperta can switch the API off. While it is off, every path under `/api/agent/v1`
> answers the site's own `404` page (HTML, no JSON body) — exactly what any unknown path on
> aperta-designs.com answers. So a `404` page from `GET /capabilities`, fetched with an HTTP client, means
> "not open": tell the person, do not retry in a loop, and offer the manual route in §2.

## 1. What Aperta makes

- An **open cuff bracelet** or an **open ring**, laser-cut from one flat sheet of **1.5 mm C260 brass**
  (annealed), then rolled by hand to the person's size. The two short ends stay open.
- The drawing *is* the design: the outline and every opening are cut exactly as drawn. Black is metal,
  white is air.
- A fixed price per product, VAT included (snapshot: bracelet ₪399 + ₪35 shipping = ₪434; ring ₪299 +
  ₪35 = ₪334 — live in `capabilities.products.*.price`). Made to order in about 10–14 working days.
- The person approves and orders on aperta-designs.com. You never pay, and you never order for them.

## 2. When to use it — and when not

Use it when a person wants a personal bracelet or ring from something that matters to them: a story, a
place, a rhythm, a motif, a mood, a pattern they describe or sketch.

Do **not** use it for:

- **Text or names.** Image models misspell and restyle letters, and what is drawn is what is cut. Send the
  person to Aperta's own studio at `https://aperta-designs.com/dialogue/create`, which cuts lettering from
  a font.
- Gemstones, colour, enamel, plating, 3D shapes, closed rings, earrings, pendants — none is made here.
- Anything the person has not asked for. One job = one design for one person.

People without an API-capable agent can still get there by hand with the Hebrew chatbot skill at
`https://aperta-designs.com/skills/aperta-duotone/SKILL.md` (they upload the image in the studio themselves).

## 3. The workflow

Seven steps. The request and response of each are in [references/api.md](references/api.md); complete
runs are in [references/examples.md](references/examples.md).

1. **Read the capabilities.** `GET /capabilities` → products, sizes, the image contract, the two
   manufacturing floors (`manufacturing.minOpeningMm`, `manufacturing.minMetalMm`), limits, price, links,
   the error codes. Cache it for the session.
2. **Size the piece and open a job.** Ask for the wrist circumference in millimetres and a fit
   (`snug` / `comfort` / `loose`) for a bracelet, or a US ring size for a ring — or an explicit flat blank
   length. Agree a width (start from `products.*.defaultWidthMm`; **`widthMm` is required**). Then
   `POST /jobs` with a fresh `Idempotency-Key` (a UUID) and `attribution.skillVersion: "1.0.0"`.
   The `201` carries `credentials.jobSecret` **once** — store it now; every job call needs it as
   `Authorization: Bearer <secret>`. `job.drawing.ratio` is the length ÷ width you must draw. A width too
   narrow for the length (the ratio would pass `image.cropAspect.max`) is refused with `400
   WIDTH_OUT_OF_RANGE`, which names the minimum width for that length.
3. **Generate the image** with your own image model: one piece, landscape, at `job.drawing.ratio`, flat
   black on pure white, on a 1536 × 1024 canvas. How to write the prompt:
   [references/prompting.md](references/prompting.md).
4. **Check it yourself** against [references/image-contract.md](references/image-contract.md) (§5 is a
   checklist). Regenerate rather than submit something you can see is wrong.
5. **Submit** the PNG: `POST /jobs/{jobId}/submissions`, raw `Content-Type: image/png` (or multipart with
   a file field named `image`, typed `image/png` or left untyped). Processing is synchronous, inside a window of
   `limits.syncTimeoutSec` (120 s) — a window, not a guaranteed ceiling: give your client a little more, and
   if it times out, resend the **same** bytes (never a new image) and read the stored outcome.
6. **Fix loop.** `422` → the design failed: fix **every** `validation.errors[]` in a new image and submit it
   to the **same** job. `200` → it is manufacturable; read `validation.warnings[]` (what the engine
   changed) and look at `files.previewSvg` before you hand it over. A job takes at most
   `limits.submissionsPerJob` (10) counted submissions.
7. **Hand off** `submission.approval.url` to the person (§6). Then read `GET /jobs/{jobId}` to learn
   `approval.status` — waiting at least `limits.pollIntervalSec` (30 s) between reads.

## 4. The image, in one paragraph

A PNG, **flat black (`#000000`) on pure white (`#FFFFFF`)**, no grey, gradients, shadows, texture or
transparency; **one connected piece** (every black region joined to the body — detached regions are
dropped); drawn **horizontally** at the job's ratio with **at least 5% white margin** on every side; every
opening **at least 0.2 mm wide** at its narrowest point and larger than `job.drawing.minOpeningAreaMm2`;
every strip of metal **at least 0.6 mm wide**; no text, stones or colour; both short ends open. PNG only,
up to 6 MB, short side ≥ 256 px, long side ≤ 8192 px, ≤ 24 megapixels — 1536 × 1024 is recommended.
The full contract, generated from the code: [references/image-contract.md](references/image-contract.md).

Sample images (real outputs of Aperta's own fixture generator, checked on the real tracer):

| File | What it shows |
| --- | --- |
| [assets/samples/bracelet-160x18-valid.png](assets/samples/bracelet-160x18-valid.png) | A 160 × 18 mm bracelet with nine openings — passes (`200 ready`, no warnings). |
| [assets/samples/bracelet-160x18-disconnected.png](assets/samples/bracelet-160x18-disconnected.png) | The same with a detached blob above the strip — `200 ready` with the warning `DISCONNECTED_REGIONS`: the blob was dropped. |
| [assets/samples/ring-56x6-valid.png](assets/samples/ring-56x6-valid.png) | A 56 × 6 mm ring with five openings — passes. |

## 5. Handling failures

Every error tells you what to do: follow its `agent_instruction`. The full catalogue, generated from the
code, is [references/errors.md](references/errors.md).

| You get | It means | Do this |
| --- | --- | --- |
| `422` with `submission.validation.errors[]` | The design cannot be made as drawn. | Fix each error at its `locations` (millimetres on the strip: `x` from the left end, `y` from the top edge) and submit a new image to the same job. Read its `validation.warnings[]` too: they often say why. |
| `200` with `validation.warnings[]` | It passed, but the engine changed something (bridged or removed an island, filled a tiny opening, widened a neck, stretched the ratio, dropped a stray region). | Look at `files.previewSvg`. Accept, or redraw where it matters. |
| `400` / `413` / `415` | The request or the file is wrong (`INVALID_REQUEST`, `WIDTH_OUT_OF_RANGE`, `INVALID_FILE`, `FILE_TOO_LARGE`, `UNSUPPORTED_FORMAT`, …). | Fix the request; the message names the field. |
| `409 JOB_LOCKED` / `JOB_EXPIRED` / `JOB_CANCELLED` / `TOO_MANY_SUBMISSIONS` | This job takes no more submissions. | Open a new job for the next design. (The same `409` on a `DELETE` means there is nothing left to cancel: do nothing.) |
| `409 SUBMISSION_IN_PROGRESS` | The same bytes are still being processed in this job. | Wait `Retry-After` (10 s), then read `GET /jobs/{jobId}/submissions/{no}` for the number in the message. Do not upload again. |
| `429 RATE_LIMITED` | A rate limit was reached. | Wait the `Retry-After` seconds, then retry. A daily limit counts per UTC day: its `Retry-After` runs to midnight UTC. |
| `502 PROCESSING_FAILED` | Processing failed on Aperta's side. It was not counted. | Wait `Retry-After` seconds and resend the **same** bytes once. If it fails again, re-export the PNG (flat black on white, 8-bit, no alpha) and submit that. Do not loop. |
| `502 VECTORIZER_UNAVAILABLE` / `503` / `500` with `retryable: true` | A technical failure on Aperta's side. It was not counted. | Wait `Retry-After` seconds and resend the **same** request once. If it fails again, stop and tell the person. |
| A `5xx` that is not JSON (an HTML error page) | The hosting platform cut the request off before the API answered. | Treat it as a lost response: wait about 30 seconds, read `GET /jobs/{jobId}`, and if `latestSubmission` does not show the upload, resend the same bytes once. |
| `404 NOT_FOUND` | Unknown job id or wrong secret (identical on purpose) — or `"No such endpoint."` for a mistyped path. | Check the id and the secret; fix the path. Open a new job only if the secret is lost. |
| `405 METHOD_NOT_ALLOWED` | Wrong method for the path. | Use a method from the `Allow` header. |

Rules that save your submissions:

- **Never resubmit an unchanged image** after a `422`. The same bytes in the same job return the stored
  answer (not counted, not rate-limited) while the job still takes submissions — useful to recover a lost
  response, useless to "try again".
- **Retry only what says `retryable: true`** (or a non-JSON `5xx`, as above), only after `Retry-After`, and
  only once or twice. Never loop.
- If a response is lost mid-upload, read `GET /jobs/{jobId}`: `latestSubmission` tells you whether it
  finished (this read works in every state; resending the bytes does not once the job is approved,
  expired or cancelled). A `failed_processing` entry with `PROCESSING_INTERRUPTED` means: send the same bytes again.

## 6. Handing off to the person

When a submission is `ready`, give the person `submission.approval.url` (`https://aperta-designs.com/a/…`).
One job has one link, and it shows the job's newest passing submission: `approval` is filled only on that
submission, and an older `ready` one reads `approval: null`. Say, in their language:

- that it opens Aperta's approval page, **which is in Hebrew** (`capabilities.approvalPage`), showing the
  design, the price and two buttons — approve or decline;
- that approving asks them to sign in (or sign up) on Aperta and then continues to the order on the site,
  where delivery, referral codes and pickup are handled;
- the price (`submission.price.total`, VAT included) and that the piece is made to order.

Then:

- While the person decides, read `GET /jobs/{jobId}` no more than once every `limits.pollIntervalSec`
  seconds (30): a person takes minutes or hours, and faster polling only spends requests.
  `approval.status` is `pending`, `approved` or `declined`. `approved` → the design now lives in their
  account; your part is done.
- `declined` → ask what to change, fix the design and submit again to the **same** job: the same approval
  link comes back to life with the new design.
- If the person abandons the design, cancel the job (`DELETE /jobs/{jobId}`) so their images are deleted
  now — an expired job is kept 180 more days before it is deleted.
- An unapproved job expires 7 days after it was created — submissions do not extend it — or 24 hours
  after a decline, whichever comes first. A passing submission after a decline cancels the 24-hour clock
  (back to creation + 7 days). `job.expiresAt` always holds the current deadline.

Never:

- approve, decline or order on the person's behalf — the approval page is theirs;
- share the job secret, or put it in any link;
- send Aperta the person's story, name, photos or any personal detail. Send only the PNG, the size, the
  width and (optionally) `attribution` about **your** platform — keep personal data out of it. Aperta
  also records the IP address you connect from, for rate limits (see [README.md](README.md#privacy)).

## 7. References

| File | Read it when |
| --- | --- |
| [references/api.md](references/api.md) | You write the HTTP calls: curl for every step, the `Idempotency-Key`, the Bearer secret, raw and multipart uploads, `422` and `409` handling, retries and `Retry-After`. |
| [references/image-contract.md](references/image-contract.md) | You prepare or check an image. **Generated from the code** — the file limits, the drawing rules, the 0.2 mm / 0.6 mm floors, the area floor, the pixel note. |
| [references/prompting.md](references/prompting.md) | You write the prompt for your image model. |
| [references/errors.md](references/errors.md) | You meet an error or a warning. **Generated from the code** — every code, what it means, what to do. |
| [references/examples.md](references/examples.md) | You want complete runs: a success, a fix loop, the human handoff, retries — with real request and response bodies. |
| [README.md](README.md) | You are a developer installing this skill or checking versions. |

The human overview of all of this: `https://aperta-designs.com/agents`.
