Sign Customiser

Move previous-generation requests to v3 without duplicating writes or losing data.

The documented v2 and unversioned public operations are deprecated. They still authenticate, validate, and return the same bodies and status codes. This change adds deprecation signals and a supported path to v3. It does not remove an operation or change its business behaviour.

The generated schedule below records the fixed deprecation instant and scheduled Sunset. The six-month migration window starts at that deprecation instant. Actual route removal still depends on observed usage, affected-consumer communication, and separate approval. Do not treat the scheduled date as permission to leave a migration until the final day.

Read the response signals

Every affected response carries the same three headers, including authentication, validation, and not-found responses:

http
Deprecation: @<unix-seconds>
Sunset: <http-date>
Link: <migration-guide-url>; rel="deprecation"; type="text/html"

Deprecation is an RFC 9745 structured-field date expressed as Unix seconds. Sunset is an RFC 8594 HTTP date. Link points to this guide and may appear alongside other Link values. These headers describe lifecycle state. They do not alter the response body.

The operation replacement table below is generated from the checked migration map. It covers all 13 deprecated request operations and names the Store-key scope required by each v3 replacement.

Replace wildcard keys

Legacy wildcard tokens continue to work during migration, including on v3, but do not carry a useful least-privilege boundary. Create a scoped key for the exact Store and operations you are moving. Start with GET /api/v3/stores/me and confirm data.id, data.platform, and data.scopes before reading or writing anything.

The API and documentation now say Store where older payloads or code may say Team. This is a terminology change at the public boundary. Do not rewrite an integer identifier merely because its label changed.

Keep the old credential until the migrated integration has passed read comparisons and a controlled write cutover. Revoke it once no previous-generation caller remains.

Update response handling

V3 success responses use an envelope:

JSON
{
"data": { "object": "customiser", "id": 42 },
"links": {
"self": "https://web.signcustomiser.com/api/v3/customisers/42",
"documentation": "https://www.signcustomiser.com/help/api/"
},
"meta": {
"api_version": "v3",
"request_id": "req_01jz9x2k7c8f3m5n6p7q8r9s0t"
}
}

Collections add pagination and links.next. Follow links.next unchanged while pagination.has_more is true. Cursors are opaque and tied to the filter set that produced them.

V3 errors use RFC 9457 application/problem+json, not the previous generation’s message plus field-keyed errors shape. Branch on the HTTP status and stable code. Keep request_id in logs and support reports. Validation problems put field details in errors[] with JSON Pointers and may include allowed_values.

JSON
{
"type": "https://www.signcustomiser.com/help/api/problems/validation-failed",
"title": "The request failed validation",
"status": 422,
"code": "validation_failed",
"detail": "Correct the fields listed in errors.",
"errors": [
{
"pointer": "/pricing_model",
"code": "unsupported_value",
"allowed_values": ["simple_letter", "advanced_letter", "frame_fit"]
}
],
"request_id": "req_01jz9x2k7c8f3m5n6p7q8r9s0t"
}

Migrate pricing documents

Read the current v3 document before changing it. GET /api/v3/customisers/{customiser_id}/pricing returns the document under data. Send the document itself, without the data, links, or meta envelope, to a write operation.

Choose the write that matches your intent:

  • PATCH applies an RFC 7396 JSON Merge Patch. Omitted properties stay unchanged, null removes a property, and arrays are replaced as a whole.
  • PUT is a full replacement. Omitted sizes, price lists, and rows are deleted. Keep every record that must survive.
  • POST /api/v3/customisers/{customiser_id}/pricing/validate runs the same validation without persisting, changing caches, or consuming an idempotency key. To dry-run a patch, apply it locally to the fetched document and validate the result.

Both persistent writes require pricing:write and an Idempotency-Key. Reusing the same key with the same method, path, and body replays the original result. Reusing it with a different body returns 409 idempotency_key_conflict.

Translate the document shape

Most structural fields remain snake_case. Apply these field changes before validation:

Previous-generation fieldv3 field or behaviour
request wrapper pricingsend the pricing document as the whole request body
response wrapper pricingread the document from data
customiser_idunchanged; optional read-only guard on writes
pricing_modelunchanged field name; translate its value with the generated glossary and treat it as a read-only guard
value_unitsremoved; read currency and the field descriptions instead
sizesunchanged collection name
sizes[].height_strategysizes[].sizing_strategy; translate every value with the generated glossary
sizes[].height_measurement_modeunchanged field name; translate LINE_HEIGHT and LETTER_HEIGHT to their lowercase values
base_pricing and its snake_case fieldsunchanged
price_lists and its snake_case fieldsunchanged, except each pricing_id becomes price_list_id
sheet_pricing.pricingssheet_pricing.rows
cmLimitWidthwidth_limit_cm
cmLimitHeightheight_limit_cm
basePricebase_price
pricePerSqCmprice_per_sq_cm
no equivalentobject, pricing_model_display_name, and currency are new read-only fields; omit them or echo the fetched values on writes
implied currency and unitsinteger amount fields use minor units; per-centimetre rates use decimal major units

The generated glossary below owns every pricing and sizing enum translation. The important distinctions are:

  • FIXED_WIDTH becomes simple_letter.
  • MATERIAL_LENGTH becomes advanced_letter.
  • FRAME_FIT becomes frame_fit.
  • The retired FIXED_HEIGHT pricing model becomes read-only legacy_fixed_height migration metadata. V3 rejects it on writes.
  • The supported fixed_height sizing strategy is different. Its older spellings are HEIGHT and FIXED.
  • The supported fixed_width sizing strategy replaces WIDTH and DYNAMIC.
  • LINE_HEIGHT becomes line_height; LETTER_HEIGHT becomes letter_height.

Pricing request diff

Previous-generation PUT wraps the document and mixes old enum values with camelCase sheet bands:

JSON
{
"pricing": {
"pricing_model": "FIXED_WIDTH",
"sizes": [
{
"size_id": 7,
"width_cm": 50,
"height_strategy": "WIDTH",
"height_measurement_mode": "LINE_HEIGHT"
}
],
"sheet_pricing": {
"pricings": [
{
"cmLimitWidth": 100,
"cmLimitHeight": 60,
"basePrice": 2000,
"pricePerSqCm": 1.5
}
]
}
}
}

V3 PUT takes the canonical document directly:

JSON
{
"pricing_model": "simple_letter",
"sizes": [
{
"size_id": 7,
"width_cm": 50,
"sizing_strategy": "fixed_width",
"height_measurement_mode": "line_height"
}
],
"sheet_pricing": {
"rows": [
{
"width_limit_cm": 100,
"height_limit_cm": 60,
"base_price": 2000,
"price_per_sq_cm": 1.5
}
]
}
}

The v3 response puts the saved document in data, then adds links and meta. Do not send those envelope fields back on a write.

Move webhook management

Webhook CRUD moves to /api/v3/webhook-subscriptions. The old PUT update becomes PATCH and accepts a merge patch for topic and url. Create, update, and delete require webhooks:write plus an Idempotency-Key. Reads require webhooks:read.

Replace GET /api/v2/webhook-examples/{topic} with GET /api/v3/webhook-event-types. The v3 catalogue returns every available topic and a static example in one response. Clients must tolerate new topic values. Subscription creation returns its signing secret once; later reads do not expose it.

The delivery payload and signature-verification contract are separate from subscription CRUD. Keep verifying the raw request body before decoding JSON.

Move integration writes

Both legacy order-create routes move to POST /api/v3/orders. The unversioned product route moves to POST /api/v3/customisers/{customiser_id}/products. These operations require enabled custom integrations and Store-scoped keys.

Translate the v2 custom-integration order

v2 order fieldv3 order field or behaviour
integration_idunchanged
external_idstore_order_id
external_order_numberstore_order_number
no equivalenttotal_amount is now required in integer minor currency units
currencyunchanged; send an uppercase ISO 4217 code
shipping_lineshipping_method
customer.*unchanged field names
billing_address.*, shipping_address.*unchanged snake_case names; v3 also accepts nullable company, email, and phone fields
products[].external_idline_items[].product_id; use the integer data.id returned by the v3 product write
products[].quantityline_items[].quantity
products[].priceremoved from the order request; v3 snapshots the price from the referenced product

The v2 response uses an order wrapper and repeats the submitted external product fields. V3 returns 201, puts the canonical order under data, projects stored products under data.line_items, and adds links and meta.request_id.

Translate the unversioned order

Unversioned fieldv3 order field or behaviour
order_idstore_order_id
order_numberstore_order_number
order_totaltotal_amount; convert decimal major units to integer minor units before sending
order_currencycurrency
emailcustomer.email
shipping_lineshipping_method
shipping_address.namesplit into shipping_address.first_name and shipping_address.last_name
shipping_address.address1, address2, zipshipping_address.address_1, address_2, postcode
other shipping address fieldsunchanged names
products[].idcreate each product through v3 first, then use its integer data.id as line_items[].product_id
legacy product artwork fieldsmove them to the v3 product write, not the order request

The unversioned response is only { "ok": true }. V3 returns the stored order resource, line-item projection, self link, and request ID.

Translate the unversioned product

Unversioned fieldv3 product field or behaviour
path {customiser}path {customiser_id}
no equivalentintegration_id, title, price_amount, and currency are now required
product_idstore_product_id
carttransform the selections you need into customisations[] records with stable snake_case key, label, and value fields
price_breakdownno direct field; calculate price_amount in minor units and keep any integration-specific breakdown outside the API resource
custom_background_pathartwork.custom_background_url
custom_background_original_pathartwork.custom_background_original_url
logo_original_pathartwork.source_file_url
product_type_pathsno generic map; send supported URLs through the matching explicit artwork field, such as preview_image_url, outline_image_url, svg_url, eps_url, illustrator_pdf_url, or dxf_url

The unversioned response is only { "ok": true }. The v3 201 response returns the product under data, including its integer ID for later order line items, plus links and meta.request_id.

The product write now names the integration and external record explicitly. Money uses integer minor units, dimensions use _cm, and artwork fields sit under artwork:

JSON
{
"integration_id": 17,
"store_product_id": "ext-product-1001",
"title": "Custom neon sign",
"price_amount": 12900,
"currency": "AUD",
"width_cm": 80.5,
"height_cm": 31.5,
"artwork": {
"preview_image_url": "https://cdn.example.com/sign.png",
"svg_url": "https://cdn.example.com/sign.svg"
}
}

A successful response returns the modern product ID under data.id. Use that ID as line_items[].product_id when creating the v3 order:

JSON
{
"integration_id": 17,
"store_order_id": "ext-order-1001",
"store_order_number": "#1001",
"total_amount": 25800,
"currency": "AUD",
"line_items": [{ "product_id": 731, "quantity": 2 }]
}

The order response returns data.object: "order", data.status: "received", the stored line-item projection, a links.self URL, and meta.request_id. received means Sign Customiser accepted the order. It does not claim payment, manufacture, fulfilment, shipping, refund, or cancellation.

Both integration writes require a unique Idempotency-Key for each logical record. A replay with the same key and body returns the original 201 and Idempotency-Replay: true. Never send one logical product or order to the previous-generation and v3 write routes in parallel.

Cut over without duplicate writes

  1. Inventory every previous-generation method and path in your integration. Match the result against the generated table below.
  2. Create a Store-scoped key with the listed read and write scopes. Confirm the Store with /api/v3/stores/me.
  3. Move reads first. Compare old and v3 data in a non-production environment while teaching the client about envelopes, pagination, problems, and canonical values.
  4. Transform a fetched pricing document, then use the validate endpoint. Test PATCH or PUT against controlled data only after validation passes.
  5. Add durable idempotency keys to every v3 write. Store each key with the logical product, order, webhook change, or pricing change.
  6. Stop the old writer before enabling its v3 replacement. Do not dual-write. If the cutover result is uncertain, retry the v3 request with the same key and byte-equivalent body.
  7. Monitor v3 responses and request_id values. Keep the old path disabled rather than using it as an automatic write fallback.
  8. Remove old reads, revoke the wildcard key, and confirm no previous-generation request remains before the scheduled Sunset.

The tables below are generated from the checked operation map and canonical glossary. Use them as the source for route, scope, pricing, and sizing translations.

Deprecation schedule

Deprecation took effect
22 July 2026
Sunset
22 January 2027

Operation replacements

Previous operation v3 replacement Required scope
POST /api/v2/orders POST /api/v3/orders orders:write
GET /api/v2/customisers GET /api/v3/customisers customisers:read
GET /api/v2/customisers/{customiser} GET /api/v3/customisers/{customiser_id} customisers:read
POST /api/orders POST /api/v3/orders orders:write
POST /api/customisers/{customiser}/products POST /api/v3/customisers/{customiser_id}/products products:write
GET /api/v2/customisers/{customiser}/pricing GET /api/v3/customisers/{customiser_id}/pricing pricing:read
PUT /api/v2/customisers/{customiser}/pricing PUT /api/v3/customisers/{customiser_id}/pricing pricing:write
GET /api/v2/webhooks GET /api/v3/webhook-subscriptions webhooks:read
POST /api/v2/webhooks POST /api/v3/webhook-subscriptions webhooks:write
GET /api/v2/webhooks/{webhook_id} GET /api/v3/webhook-subscriptions/{webhook_id} webhooks:read
PUT /api/v2/webhooks/{webhook_id} PATCH /api/v3/webhook-subscriptions/{webhook_id} webhooks:write
DELETE /api/v2/webhooks/{webhook_id} DELETE /api/v3/webhook-subscriptions/{webhook_id} webhooks:write
GET /api/v2/webhook-examples/{topic} GET /api/v3/webhook-event-types webhooks:read

Finish kind

Field: finish_kind

Canonical API valueLegacy spellingCurrent UI labelNotes
colourCOLOURColour
textureTEXTURETexture

Height measurement mode

Field: height_measurement_mode

Canonical API valueLegacy spellingCurrent UI labelNotes
line_heightLINE_HEIGHTLine height
letter_heightLETTER_HEIGHTLetter height

Pricing model

Field: pricing_model

Canonical API valueLegacy spellingCurrent UI labelNotes
simple_letterFIXED_WIDTHSimple Letter
advanced_letterMATERIAL_LENGTHAdvanced Letter
frame_fitFRAME_FITFrame Fit
legacy_fixed_heightFIXED_HEIGHTFixed Height (legacy)Read-only legacy value. Read-only migration metadata. It is not accepted on writes.

Product family

Field: product_family

Canonical API valueLegacy spellingCurrent UI labelNotes
neonNEONNeon
channel_letterCHANNELChannel Letter
metal_cut_outCUTOUT_METALMetal Cut-Out

Selection mode

Field: selection_mode

Canonical API valueLegacy spellingCurrent UI labelNotes
single_styleSINGLESingle
per_wordMULTIMultiple

Sign category

Field: sign_category

Canonical API valueLegacy spellingCurrent UI labelNotes
neonNEONNeon
acrylic_channel_letterACRYLICChannel Letter
metal_channel_letterMETALMetal Channel Letter
metal_cut_outCUTOUT_METALMetal Cut-Out
stencilSTENCILStencil
lightboxLIGHTBOXLightbox
wood_letterWOODWood Letter
aluminium_plateALUMINIUMAluminium Plate
braille_signBRAILLEBraille Sign
wood_frameWOOD_FRAMEWood Frame

Sizing strategy

Field: sizing_strategy

Canonical API valueLegacy spellingCurrent UI labelNotes
fixed_widthWIDTH, DYNAMICFixed Width - Height scales dynamically
fixed_heightHEIGHT, FIXEDFixed Height - Width scales dynamicallySupported sizing strategy; this is not the retired FIXED_HEIGHT pricing model.