{"openapi":"3.1.0","info":{"title":"ModernMail API","version":"2026-10-01","description":"Upload a document, get a quote and post letters through Royal Mail, paid from your prepaid credit. Authenticate with `Authorization: Bearer mm_live_…` (create keys under Account → Developers). A sandbox key (`mm_test_…`) takes the same calls but simulates orders: nothing is charged, printed or posted, and responses say `livemode: false`. All amounts are integer pence in GBP. Order creation requires an `Idempotency-Key` header.","contact":{"email":"info@modernmail.co.uk"}},"servers":[{"url":"https://modernmail.co.uk/api/v1"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Account"},{"name":"Pricing"},{"name":"Files"},{"name":"Addresses"},{"name":"Quotes"},{"name":"Orders"},{"name":"Webhooks"}],"paths":{"/account":{"get":{"tags":["Account"],"summary":"Who the key acts as, credit balance and today's spend","x-scope":"account:read","responses":{"200":{"description":"The account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pricing":{"get":{"tags":["Pricing"],"summary":"Current rate card","description":"No API key required. Postage services with prices and delivery aims, envelope, paper and print options, extra-page cost and volume discount tiers. Cacheable for five minutes.","security":[],"responses":{"200":{"description":"Postage, envelope, print and paper prices and volume tiers"},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/files":{"post":{"tags":["Files"],"summary":"Ingest a PDF or Word document","x-scope":"files:write","description":"Send `multipart/form-data` with a `file` part (≤ 4 MB), or JSON with `url` (https only, fetched server-side, ≤ 50 MB), `base64`, or `presign: true` for a PUT URL you upload to yourself, followed by POST /files/{id}/finalize. The file type is detected from its bytes; PDFs containing scripts, launch actions or embedded files are refused.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}},"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"url":{"type":"string","maxLength":2048,"format":"uri","description":"Public https URL of a PDF or Word document; fetched server-side. http is refused."},"filename":{"type":"string","minLength":1,"maxLength":200}},"required":["url"]},{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":200},"base64":{"type":"string","minLength":1,"maxLength":5592410,"description":"Base64 (standard or URL-safe) of the document bytes; inline limit applies."}},"required":["filename","base64"]},{"type":"object","properties":{"presign":{"type":"boolean","const":true},"filename":{"type":"string","minLength":1,"maxLength":200},"contentType":{"type":"string","enum":["application/pdf","application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document"]},"sizeBytes":{"type":"integer","minimum":1,"maximum":52428800}},"required":["presign","filename","contentType","sizeBytes"]}]}}}},"responses":{"201":{"description":"The file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden, missing scope or daily spend cap exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Payload too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Unsupported media type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The document could not be processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/files/{id}":{"get":{"tags":["Files"],"summary":"A file's status and page count","x-scope":"files:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/files/{id}/finalize":{"post":{"tags":["Files"],"summary":"Count pages and seal a presigned upload","x-scope":"files:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The file, now ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict (price changed, idempotency key reused, file not ready)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Payload too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The document could not be processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/addresses/validate":{"post":{"tags":["Addresses"],"summary":"Check a UK address against Royal Mail's address file","x-scope":"addresses:validate","description":"Send the recipient fields of a letter. With address lines, the result says whether the address is on the file as typed (`verified`), closely matches one address (`corrected`, with the address as Royal Mail writes it in `address`), or fits nothing at that postcode (`unmatched`, with `candidates`). With a postcode alone it lists every address at it (`lookup`). UK only. Each call is a paid lookup on our side, so it is capped per key per day (`lookups` in the response; 429 `lookup_cap_exceeded` when spent) as well as per minute.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"recipientName":{"description":"Helps match a business name on the address file.","type":"string","maxLength":200},"recipientAddressLine1":{"type":"string","maxLength":200},"recipientAddressLine2":{"type":"string","maxLength":200},"recipientAddressLine3":{"type":"string","maxLength":200},"recipientCity":{"type":"string","maxLength":100},"recipientPostcode":{"type":"string","minLength":5,"maxLength":8,"description":"A UK postcode; the lookup key."},"recipientCountry":{"description":"Must be GB (the default): only UK addresses can be checked.","type":"string","maxLength":2}},"required":["recipientPostcode"]}}}},"responses":{"200":{"description":"The check","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddressValidation"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden, missing scope or daily spend cap exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/quotes":{"post":{"tags":["Quotes"],"summary":"Price an order without creating it","x-scope":"orders:read","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"letters":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"object","properties":{"recipientName":{"type":"string","minLength":1,"maxLength":200},"recipientAddressLine1":{"type":"string","minLength":1,"maxLength":200},"recipientAddressLine2":{"type":"string","maxLength":200},"recipientAddressLine3":{"type":"string","maxLength":200},"recipientCity":{"type":"string","maxLength":100},"recipientPostcode":{"default":"","type":"string","maxLength":20},"recipientCountry":{"default":"GB","type":"string","maxLength":2},"returnName":{"type":"string","maxLength":200},"returnAddressLine1":{"type":"string","maxLength":200},"returnAddressLine2":{"type":"string","maxLength":200},"returnCity":{"type":"string","maxLength":100},"returnPostcode":{"type":"string","maxLength":10},"postageType":{"type":"string","enum":["second_class","first_class","second_class_signed","first_class_signed","tracked_48","tracked_24","special_delivery","international_standard"]},"colourPrint":{"default":"bw","type":"string","enum":["bw","colour"]},"paperQuality":{"default":"standard","type":"string","enum":["standard","premium"]},"envelopeStyle":{"default":"white_c5","type":"string","enum":["white_c5","c5_boardback","a4_boardback","c5_window"]},"quantity":{"default":1,"type":"integer","minimum":1,"maximum":100},"uploadIds":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"finishing":{"type":"object","propertyNames":{"type":"string","maxLength":64},"additionalProperties":{"type":"object","properties":{"singleSidedRanges":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"from":{"type":"integer","minimum":1,"maximum":10000},"to":{"type":"integer","minimum":1,"maximum":10000}},"required":["from","to"]}},{"type":"null"}]},"stapleGroups":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"from":{"type":"integer","minimum":1,"maximum":10000},"to":{"type":"integer","minimum":1,"maximum":10000}},"required":["from","to"]}},{"type":"null"}]},"stapleBundle":{"anyOf":[{"type":"integer","minimum":1,"maximum":50},{"type":"null"}]}},"required":["singleSidedRanges","stapleGroups"]}}},"required":["recipientName","recipientAddressLine1","recipientCity","postageType","uploadIds"]}}},"required":["letters"]}}}},"responses":{"200":{"description":"The quote","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden, missing scope or daily spend cap exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict (price changed, idempotency key reused, file not ready)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/orders":{"get":{"tags":["Orders"],"summary":"List orders, newest first","x-scope":"orders:read","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["created","paid","processing","completed","cancelled","refunded"]}},{"name":"reference","in":"query","schema":{"type":"string"}},{"name":"mine","in":"query","schema":{"type":"string","enum":["1"]},"description":"Only orders placed with this key"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of orders","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"type":"object"}},"hasMore":{"type":"boolean"},"nextCursor":{"type":["string","null"]}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden, missing scope or daily spend cap exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Orders"],"summary":"Create and pay for an order from credit","x-scope":"orders:write","description":"All-or-nothing: the order is created and paid in one step, or refused with 402 and the shortfall. Pass the quote's `expectedTotalPence` to be refused with 409 `price_changed` if prices moved. Pass `postOn` (a UK working day, tomorrow to 12 months ahead) to pay now and have the order held until that day; it is refused with 400 `post_on_invalid` or 409 `scheduling_unavailable`. Or pass `deliverBy` (a day Royal Mail delivers on) to have the posting day worked out from each letter's postage so it arrives by then; a service that can't make it is refused with 409 `deliver_by_unreachable` and each service's earliest date. Requires `Idempotency-Key`; the same key and body replays the first response for 24 hours.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"letters":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"object","properties":{"recipientName":{"type":"string","minLength":1,"maxLength":200},"recipientAddressLine1":{"type":"string","minLength":1,"maxLength":200},"recipientAddressLine2":{"type":"string","maxLength":200},"recipientAddressLine3":{"type":"string","maxLength":200},"recipientCity":{"type":"string","maxLength":100},"recipientPostcode":{"default":"","type":"string","maxLength":20},"recipientCountry":{"default":"GB","type":"string","maxLength":2},"returnName":{"type":"string","maxLength":200},"returnAddressLine1":{"type":"string","maxLength":200},"returnAddressLine2":{"type":"string","maxLength":200},"returnCity":{"type":"string","maxLength":100},"returnPostcode":{"type":"string","maxLength":10},"postageType":{"type":"string","enum":["second_class","first_class","second_class_signed","first_class_signed","tracked_48","tracked_24","special_delivery","international_standard"]},"colourPrint":{"default":"bw","type":"string","enum":["bw","colour"]},"paperQuality":{"default":"standard","type":"string","enum":["standard","premium"]},"envelopeStyle":{"default":"white_c5","type":"string","enum":["white_c5","c5_boardback","a4_boardback","c5_window"]},"quantity":{"default":1,"type":"integer","minimum":1,"maximum":100},"uploadIds":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"finishing":{"type":"object","propertyNames":{"type":"string","maxLength":64},"additionalProperties":{"type":"object","properties":{"singleSidedRanges":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"from":{"type":"integer","minimum":1,"maximum":10000},"to":{"type":"integer","minimum":1,"maximum":10000}},"required":["from","to"]}},{"type":"null"}]},"stapleGroups":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"from":{"type":"integer","minimum":1,"maximum":10000},"to":{"type":"integer","minimum":1,"maximum":10000}},"required":["from","to"]}},{"type":"null"}]},"stapleBundle":{"anyOf":[{"type":"integer","minimum":1,"maximum":50},{"type":"null"}]}},"required":["singleSidedRanges","stapleGroups"]}}},"required":["recipientName","recipientAddressLine1","recipientCity","postageType","uploadIds"]}},"expectedTotalPence":{"type":"integer","minimum":0,"maximum":9007199254740991},"reference":{"type":"string","maxLength":120},"postOn":{"description":"Post on this UK working day (YYYY-MM-DD), from tomorrow up to 12 months ahead. Paid now, held until then; cancel free until it is released that morning. Omit to post on the next posting day.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"deliverBy":{"description":"Arrive by this UK date (YYYY-MM-DD; Mon–Sat, not a bank holiday). We post on the last working day that meets Royal Mail's aim for each letter's postage and hold the order until then; an aim, not a guarantee. Not together with postOn.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"required":["letters"]}}}},"responses":{"201":{"description":"The paid order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Insufficient credit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden, missing scope or daily spend cap exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict (price changed, idempotency key reused, file not ready)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/orders/{id}":{"get":{"tags":["Orders"],"summary":"One order with its letters","x-scope":"orders:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/orders/{id}/cancel":{"post":{"tags":["Orders"],"summary":"Cancel an order for a full refund","x-scope":"orders:write","description":"The same rule as the Cancel button in the account: only while the order is paid and nothing on it has been started (no letter printed, labelled or picked up by Dispatch), which in practice means the same day, before processing begins. Credit-paid orders get their credit back at once; a card-paid order is refunded to the card. The order comes back with every letter `cancelled` and its status `refunded` (everything paid was returned) or `cancelled` (nothing was owed). Otherwise 409 `order_not_cancellable` with `details.status`; ask support to cancel instead. No request body.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The cancelled order, plus what was refunded","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Order"},{"type":"object","properties":{"refund":{"type":"object","properties":{"cardPence":{"type":"integer"},"creditPence":{"type":"integer"}}}}}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden, missing scope or daily spend cap exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict (price changed, idempotency key reused, file not ready)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhook endpoints","x-scope":"webhooks:manage","responses":{"200":{"description":"Endpoints"},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden, missing scope or daily spend cap exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Webhooks"],"summary":"Register an https endpoint","x-scope":"webhooks:manage","description":"Deliveries are POSTed with `x-modernmail-signature: t=<unix>,v1=<hex>` where v1 is HMAC-SHA256 over `${t}.${body}` using the secret returned here, and `x-modernmail-delivery` carries a unique id for de-duplication. The body-only `x-modernmail-signature-legacy` header is deprecated and stops being sent after 31 December 2026.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","maxLength":2048,"format":"uri","description":"Must be https and publicly reachable."},"events":{"minItems":1,"type":"array","items":{"type":"string","enum":["order.paid","order.refunded","order.status_changed","order.released","letter.dispatched"]}},"label":{"type":"string","maxLength":60}},"required":["url","events"]}}}},"responses":{"201":{"description":"The endpoint, with its secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden, missing scope or daily spend cap exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict (price changed, idempotency key reused, file not ready)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{id}":{"get":{"tags":["Webhooks"],"summary":"One endpoint","x-scope":"webhooks:manage","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The endpoint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Webhooks"],"summary":"Remove an endpoint","x-scope":"webhooks:manage","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted"},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key from Account → Developers. Scopes: `account:read` (GET /v1/account); `files:write` (POST /v1/files, POST /v1/files/{id}/finalize and GET /v1/files/{id}); `orders:read` (POST /v1/quotes, GET /v1/orders and GET /v1/orders/{id}); `orders:write` (POST /v1/orders (spends credit) and POST /v1/orders/{id}/cancel); `addresses:validate` (POST /v1/addresses/validate (Royal Mail address file lookups, capped per key per day)); `webhooks:manage` (GET, POST and DELETE /v1/webhooks)."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_request","idempotency_key_required","unauthorized","insufficient_credit","insufficient_scope","api_unavailable","forbidden","spend_cap_exceeded","not_found","post_on_invalid","deliver_by_invalid","price_changed","scheduling_unavailable","deliver_by_unreachable","upload_not_ready","idempotency_key_reused","idempotency_in_progress","order_not_cancellable","conflict","payload_too_large","unsupported_media_type","rate_limited","lookup_cap_exceeded","unavailable","internal"]},"message":{"type":"string"},"details":{}}}}},"File":{"type":"object","properties":{"object":{"const":"file"},"id":{"type":"string"},"filename":{"type":"string"},"contentType":{"type":"string"},"sizeBytes":{"type":"integer"},"status":{"type":"string","enum":["ready","pending"]},"pageCount":{"type":["integer","null"]},"pageCountEstimated":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"upload":{"description":"Present only for presigned uploads: PUT the bytes here, then POST /files/{id}/finalize.","type":"object","properties":{"method":{"const":"PUT"},"url":{"type":"string"},"headers":{"type":"object","additionalProperties":{"type":"string"}},"expiresInSeconds":{"type":"integer"}}}}},"Address":{"type":"object","description":"A Royal Mail address in the letter's recipient field names, ready to spread into a letter.","properties":{"id":{"type":["string","null"],"description":"Royal Mail delivery point id (UDPRN)."},"organisation":{"type":["string","null"],"description":"Business at this delivery point, or null for a home."},"recipientAddressLine1":{"type":"string"},"recipientAddressLine2":{"type":["string","null"]},"recipientAddressLine3":{"type":["string","null"]},"recipientCity":{"type":"string"},"recipientPostcode":{"type":"string"},"recipientCountry":{"const":"GB"}}},"AddressValidation":{"type":"object","properties":{"object":{"const":"address_validation"},"postcode":{"type":"string","description":"The postcode as Royal Mail writes it."},"status":{"type":"string","enum":["verified","corrected","unmatched","lookup","postcode_unknown"],"description":"verified: the typed address is on the file as typed. corrected: it closely matches one address, returned in `address`. unmatched: the postcode exists but nothing fits; `candidates` lists what is there. lookup: no lines were sent; `candidates` is every address at the postcode. postcode_unknown: Royal Mail has nothing at that postcode."},"confidence":{"type":"number","description":"0 to 1; 1 for verified, the match score for corrected, 0 otherwise."},"address":{"oneOf":[{"$ref":"#/components/schemas/Address"},{"type":"null"}]},"candidates":{"type":"array","items":{"$ref":"#/components/schemas/Address"}},"lookups":{"type":"object","properties":{"usedToday":{"type":"integer"},"dailyCap":{"type":"integer"}}}}},"Letter":{"type":"object","properties":{"object":{"const":"letter"},"id":{"type":"string"},"status":{"type":"string","enum":["pending","processing","posted","cancelled"]},"recipient":{"type":"object"},"returnAddress":{"type":["object","null"]},"postageType":{"type":"string"},"colourPrint":{"type":"boolean"},"paperQuality":{"type":"string"},"envelopeStyle":{"type":"string"},"pages":{"type":"integer"},"quantity":{"type":"integer"},"pricePence":{"type":["integer","null"]},"trackingNumber":{"type":["string","null"]},"dispatchedAt":{"type":["string","null"],"format":"date-time"},"cancelledAt":{"type":["string","null"],"format":"date-time"}}},"Order":{"type":"object","properties":{"object":{"const":"order"},"id":{"type":"string"},"orderNumber":{"type":"string"},"status":{"type":"string","enum":["created","paid","processing","completed","cancelled","refunded"]},"statusLabel":{"type":"string"},"source":{"type":"string","enum":["web","api","mcp"]},"livemode":{"type":"boolean","description":"False for an order placed with a sandbox (mm_test_) key: simulated, never charged, printed or posted."},"reference":{"type":["string","null"]},"currency":{"type":"string"},"amounts":{"type":"object","properties":{"subtotalPence":{"type":"integer"},"accountDiscountPence":{"type":"integer"},"volumeDiscountPercent":{"type":"integer"},"volumeDiscountPence":{"type":"integer"},"promoDiscountPence":{"type":"integer"},"totalPence":{"type":"integer"}}},"letters":{"type":"array","items":{"$ref":"#/components/schemas/Letter"}},"url":{"type":"string"},"paidAt":{"type":["string","null"],"format":"date-time"},"postOn":{"type":["string","null"],"format":"date","description":"Scheduled letters: the UK posting day asked for (YYYY-MM-DD); null when posting on the normal schedule."},"postOnReleasedAt":{"type":["string","null"],"format":"date-time","description":"When a scheduled order was released to the dispatch queue; null while it waits for its day."},"deliverBy":{"type":["string","null"],"format":"date","description":"Deliver by a date: the arrival date the posting day was worked out from (YYYY-MM-DD); null otherwise."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Quote":{"type":"object","properties":{"object":{"const":"quote"},"currency":{"const":"gbp"},"letters":{"type":"array","items":{"type":"object"}},"envelopeCount":{"type":"integer"},"subtotalPence":{"type":"integer"},"accountDiscountPence":{"type":"integer"},"volumeDiscountPercent":{"type":"integer"},"volumeDiscountPence":{"type":"integer"},"totalPence":{"type":"integer"},"expectedTotalPence":{"type":"integer","description":"Pass back as expectedTotalPence on POST /orders."},"credit":{"type":"object","properties":{"balancePence":{"type":"integer"},"sufficient":{"type":"boolean"},"shortfallPence":{"type":"integer"}}},"dailyCap":{"type":"object","properties":{"capPence":{"type":"integer"},"spentTodayPence":{"type":"integer"},"fits":{"type":"boolean"}}}}},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"label":{"type":["string","null"]},"secretHint":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"disabledReason":{"type":["string","null"]},"consecutiveFailures":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"lastDelivery":{"type":["object","null"]},"secret":{"type":"string","description":"Only on creation. HMAC-SHA256 signing secret; store it."}}},"Account":{"type":"object","properties":{"object":{"const":"account"},"livemode":{"type":"boolean","description":"False for a sandbox (mm_test_) key."},"type":{"type":"string","enum":["personal","business"]},"name":{"type":["string","null"]},"email":{"type":"string"},"business":{"type":["object","null"]},"credit":{"type":"object","properties":{"currency":{"const":"gbp"},"balancePence":{"type":"integer"},"topUpUrl":{"type":"string"}}},"key":{"type":"object"},"permissions":{"type":"object"}}},"OrderRequest":{"type":"object","properties":{"letters":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"object","properties":{"recipientName":{"type":"string","minLength":1,"maxLength":200},"recipientAddressLine1":{"type":"string","minLength":1,"maxLength":200},"recipientAddressLine2":{"type":"string","maxLength":200},"recipientAddressLine3":{"type":"string","maxLength":200},"recipientCity":{"type":"string","maxLength":100},"recipientPostcode":{"default":"","type":"string","maxLength":20},"recipientCountry":{"default":"GB","type":"string","maxLength":2},"returnName":{"type":"string","maxLength":200},"returnAddressLine1":{"type":"string","maxLength":200},"returnAddressLine2":{"type":"string","maxLength":200},"returnCity":{"type":"string","maxLength":100},"returnPostcode":{"type":"string","maxLength":10},"postageType":{"type":"string","enum":["second_class","first_class","second_class_signed","first_class_signed","tracked_48","tracked_24","special_delivery","international_standard"]},"colourPrint":{"default":"bw","type":"string","enum":["bw","colour"]},"paperQuality":{"default":"standard","type":"string","enum":["standard","premium"]},"envelopeStyle":{"default":"white_c5","type":"string","enum":["white_c5","c5_boardback","a4_boardback","c5_window"]},"quantity":{"default":1,"type":"integer","minimum":1,"maximum":100},"uploadIds":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"finishing":{"type":"object","propertyNames":{"type":"string","maxLength":64},"additionalProperties":{"type":"object","properties":{"singleSidedRanges":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"from":{"type":"integer","minimum":1,"maximum":10000},"to":{"type":"integer","minimum":1,"maximum":10000}},"required":["from","to"]}},{"type":"null"}]},"stapleGroups":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"from":{"type":"integer","minimum":1,"maximum":10000},"to":{"type":"integer","minimum":1,"maximum":10000}},"required":["from","to"]}},{"type":"null"}]},"stapleBundle":{"anyOf":[{"type":"integer","minimum":1,"maximum":50},{"type":"null"}]}},"required":["singleSidedRanges","stapleGroups"]}}},"required":["recipientName","recipientAddressLine1","recipientCity","postageType","uploadIds"]}},"expectedTotalPence":{"type":"integer","minimum":0,"maximum":9007199254740991},"reference":{"type":"string","maxLength":120},"postOn":{"description":"Post on this UK working day (YYYY-MM-DD), from tomorrow up to 12 months ahead. Paid now, held until then; cancel free until it is released that morning. Omit to post on the next posting day.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"deliverBy":{"description":"Arrive by this UK date (YYYY-MM-DD; Mon–Sat, not a bank holiday). We post on the last working day that meets Royal Mail's aim for each letter's postage and hold the order until then; an aim, not a guarantee. Not together with postOn.","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"required":["letters"]},"QuoteRequest":{"type":"object","properties":{"letters":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"object","properties":{"recipientName":{"type":"string","minLength":1,"maxLength":200},"recipientAddressLine1":{"type":"string","minLength":1,"maxLength":200},"recipientAddressLine2":{"type":"string","maxLength":200},"recipientAddressLine3":{"type":"string","maxLength":200},"recipientCity":{"type":"string","maxLength":100},"recipientPostcode":{"default":"","type":"string","maxLength":20},"recipientCountry":{"default":"GB","type":"string","maxLength":2},"returnName":{"type":"string","maxLength":200},"returnAddressLine1":{"type":"string","maxLength":200},"returnAddressLine2":{"type":"string","maxLength":200},"returnCity":{"type":"string","maxLength":100},"returnPostcode":{"type":"string","maxLength":10},"postageType":{"type":"string","enum":["second_class","first_class","second_class_signed","first_class_signed","tracked_48","tracked_24","special_delivery","international_standard"]},"colourPrint":{"default":"bw","type":"string","enum":["bw","colour"]},"paperQuality":{"default":"standard","type":"string","enum":["standard","premium"]},"envelopeStyle":{"default":"white_c5","type":"string","enum":["white_c5","c5_boardback","a4_boardback","c5_window"]},"quantity":{"default":1,"type":"integer","minimum":1,"maximum":100},"uploadIds":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"finishing":{"type":"object","propertyNames":{"type":"string","maxLength":64},"additionalProperties":{"type":"object","properties":{"singleSidedRanges":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"from":{"type":"integer","minimum":1,"maximum":10000},"to":{"type":"integer","minimum":1,"maximum":10000}},"required":["from","to"]}},{"type":"null"}]},"stapleGroups":{"anyOf":[{"maxItems":100,"type":"array","items":{"type":"object","properties":{"from":{"type":"integer","minimum":1,"maximum":10000},"to":{"type":"integer","minimum":1,"maximum":10000}},"required":["from","to"]}},{"type":"null"}]},"stapleBundle":{"anyOf":[{"type":"integer","minimum":1,"maximum":50},{"type":"null"}]}},"required":["singleSidedRanges","stapleGroups"]}}},"required":["recipientName","recipientAddressLine1","recipientCity","postageType","uploadIds"]}}},"required":["letters"]},"AddressValidateRequest":{"type":"object","properties":{"recipientName":{"description":"Helps match a business name on the address file.","type":"string","maxLength":200},"recipientAddressLine1":{"type":"string","maxLength":200},"recipientAddressLine2":{"type":"string","maxLength":200},"recipientAddressLine3":{"type":"string","maxLength":200},"recipientCity":{"type":"string","maxLength":100},"recipientPostcode":{"type":"string","minLength":5,"maxLength":8,"description":"A UK postcode; the lookup key."},"recipientCountry":{"description":"Must be GB (the default): only UK addresses can be checked.","type":"string","maxLength":2}},"required":["recipientPostcode"]}}}}