# Moradas > Free public API for Portuguese address autocomplete and postal codes (CP7). No API key, no sign-up, CORS open. Available as a REST API, an embeddable JavaScript widget and a remote MCP server for AI agents. Moradas suggests Portuguese streets and localities as the user types and returns the exact 7-digit postal code (CP7, format `NNNN-NNN`), picking it by door number when a street spans several postal codes. Field names are Portuguese: `localidade` (postal locality), `concelho` (municipality), `distrito` (district), `cp7` (postal code). Base URL: `https://moradas.dev`. Endpoints: - `GET /suggest?q=&count=` — autocomplete. 4–7 digits are treated as a postal code, text as a street or locality; a trailing door number (`av liberdade lisboa 196`) picks the exact CP7 (`resolved_cp7`). `count` defaults to 10, maximum 20. The beginning of a municipality name (`lisb`) returns the municipality itself first (`kind: loc`, `art_id: 0`, `cp7: null`). If a result was only found after correcting a typo (`rua agusta` → Rua Augusta), `data` carries `corrected: true`. - `GET /resolve?art=&numero=` — exact CP7 for a street (`art_id` from `/suggest`) and a door number, plus the street's postal-code segments. Use it when a street suggestion has `cp7: null`. A suggestion with `kind: "loc"`, `art_id: 0` and `cp7: null` is a whole locality or municipality (e.g. the user typed just "Lisboa"): there is no street to resolve — let the user keep typing. - `GET /cp/{cp7}` — postal code card: localidade, concelho, distrito and the streets it covers (`arterias[].street`). - `GET /cp/{cp4}` — the first four digits only: every CP7 in that area, in order, each with localidade, concelho, distrito and `cliente` (the organisation's name when the code belongs to it alone, otherwise null). - `POST /feedback` — JSON `{"query": "...", "note": "..."}` to report an address that did not come up. - `POST /batch` — JSON `{"items":[{"id":"...","q":"..."}]}`: up to 100 addresses in one request for programs that hold a list (order import, CRM, database clean-up). Each item is checked like `/suggest`; results come back in input order with `status` `ok` (postal code in `cp7`), `ambiguous` (several candidates, or a street with several postal codes and no door number, or just a locality) or `not_found`, plus `suggestion` — the first suggestion in the same shape as `/suggest` (`null` for `not_found`). `id` is any string or number of yours, echoed back (`null` when not sent). A batch of N items counts as N requests against the rate limit, so send up to 50 per call; body up to 64 KB; more than 100 items → 400, larger body → 413, invalid JSON → 400, `GET /batch` → 405 with `X-Moradas-Hint`. Responses are not cached. - `GET /health` — service status. There is no `/v1`, `/api` or `/api/v1` prefix and no `/cp/suggest`: the paths above are at the root of `https://moradas.dev`. `/cp/` takes the postal code alone (`/cp/1100-413`, not `/cp/1100-413 LISBOA`). There are no `ruas`, `nome`, `rua` or `results` fields. Literal responses (real, 30 September 2026). `art_id` values change with every weekly data refresh: take them from a fresh `/suggest` response, never from these examples. `GET /suggest?q=estrada%20malveira%20da%20serra%20920&count=2` ```json {"suggestions":[{"value":"Estrada Malveira da Serra, Malveira da Serra","data":{"art_id":125494,"kind":"street","street":"Estrada Malveira da Serra","localidade":"Malveira da Serra","concelho":"Cascais","distrito":"Lisboa","cp7":"2755-332","nseg":1,"numero":"920","resolved_cp7":"2755-332"}},{"value":"Estrada Malveira da Serra, Aldeia de Juzo","data":{"art_id":126122,"kind":"street","street":"Estrada Malveira da Serra","localidade":"Aldeia de Juzo","concelho":"Cascais","distrito":"Lisboa","cp7":"2750-834","nseg":11,"numero":"920","resolved_cp7":"2750-834"}}]} ``` `GET /resolve?art=132467&numero=10` (`art_id` of "Rua das Adelas, Lisboa" from a fresh `/suggest`) ```json {"resolved":{"value":"Rua das Adelas, Lisboa","data":{"art_id":132467,"kind":"street","street":"Rua das Adelas","localidade":"Lisboa","concelho":"Lisboa","distrito":"Lisboa","cp7":"1200-008","nseg":2}},"segments":[{"cp7":"1200-007","label":"Impares de 3 a 17A"},{"cp7":"1200-008","label":"Pares de 2 a 28"}]} ``` `GET /cp/1000-098` ```json {"cp7":"1000-098","cp4":"1000","cp3":"098","distrito":"Lisboa","concelho":"Lisboa","localidade":"Lisboa","arterias":[{"art_id":130446,"street":"Praça do Chile","troco":null,"porta":null,"cliente":null}]} ``` `GET /cp/4700` (shortened here to the first two codes) ```json {"cp4":"4700","codigos":[{"cp7":"4700-001","localidade":"Braga","concelho":"Braga","distrito":"Braga","cliente":null},{"cp7":"4700-002","localidade":"Braga","concelho":"Braga","distrito":"Braga","cliente":null}]} ``` `POST /batch` with the body `{"items":[{"id":"o-1001","q":"Avenida da Liberdade 196, Lisboa"},{"id":"o-1002","q":"Rua Augusta 100 Lisboa"},{"id":"o-1003","q":"xyzxyz qqq"},{"id":"o-1004","q":"rua das flores"}]}` (real response, 2 October 2026) ```json {"results":[{"id":"o-1001","q":"Avenida da Liberdade 196, Lisboa","status":"ok","cp7":"1250-147","suggestion":{"value":"Avenida da Liberdade, Lisboa","data":{"art_id":129534,"kind":"street","street":"Avenida da Liberdade","localidade":"Lisboa","concelho":"Lisboa","distrito":"Lisboa","cp7":"1250-147","nseg":28,"numero":"196","resolved_cp7":"1250-147"}}},{"id":"o-1002","q":"Rua Augusta 100 Lisboa","status":"ok","cp7":"1100-053","suggestion":{"value":"Rua Augusta, Lisboa","data":{"art_id":130802,"kind":"street","street":"Rua Augusta","localidade":"Lisboa","concelho":"Lisboa","distrito":"Lisboa","cp7":"1100-053","nseg":12,"numero":"100","resolved_cp7":"1100-053"}}},{"id":"o-1003","q":"xyzxyz qqq","status":"not_found","cp7":null,"suggestion":null},{"id":"o-1004","q":"rua das flores","status":"ambiguous","cp7":null,"suggestion":{"value":"Rua das Flores, Lisboa","data":{"art_id":132486,"kind":"street","street":"Rua das Flores","localidade":"Lisboa","concelho":"Lisboa","distrito":"Lisboa","cp7":null,"nseg":5}}}]} ``` Usage terms: - Free, including commercial use. - Fair-use limit of about 50 requests per 10 seconds per IP; HTTP 429 when exceeded. On 429, wait for the `Retry-After` header if present, otherwise about 10 seconds, then retry. - HTTP 404: `/cp/{cp7}` for a postal code that does not exist, `/resolve` for an unknown `art_id`. The body stays `{"error":"CP7 not found"}` or `{"error":"not found"}`; the `X-Moradas-Hint` response header says why, e.g. `4700-000 is not an assigned postal code; /cp/4700 lists the postal codes that start with 4700` or `Use only the postal code, without locality or other text: /cp/1100-413`. `/suggest` never returns 404; no matches give an empty list. Check the CP7 format with `^\d{4}-\d{3}$` before calling `/cp`. - No uptime guarantee — cache what you need. - Stable v1 contract: fields are never renamed or removed, only added. - `art_id` is ephemeral: it changes when the data is refreshed. Use it right after `/suggest` and never store it. - Status: beta. Contact: dev@moradas.dev Widget: `https://moradas.dev/widget.js` runs in the browser and defines `window.PTAddress`. Load it without `async` and call `attach` once the input exists, e.g. at the end of ``: ```html ``` `PTAddress.attach(input, options)` returns `{ destroy() }`, which removes the dropdown and the input's listeners. Options: - `fill`: `{ postal, city, municipality, district }`, each a CSS selector or an element; all optional. - `onSelect(data)`: called when a suggestion is picked; `data` is that suggestion's `data` object from `/suggest`. - `onResolve(cp7)`: called when the exact CP7 is known: on the pick or, for a street with several postal codes, once the door number is typed and the field loses focus. - `lang`: `'pt'` (default) or `'en'`, the language of the dropdown messages. - `minChars` (default 2), `debounce` in milliseconds (default 160), `count` (default 8, cap 20), `endpoint` (default `https://moradas.dev`). - `beacon` (default `true`): error reports, see below; `false` turns them off. Error reports: when the service does not answer properly (HTTP 429, 5xx, a network error or an unexpected reply), the widget sends one small `POST` with `navigator.sendBeacon` to `{endpoint}/widget-error`, e.g. `{"kind":"429","widget_version":"0.3.0","http_status":429}` — at most one per error kind and page load. No address, no typed text and no page URL; the browser adds the site's origin. They are counted per hour and per site. Set `beacon: false` to disable them. The widget fills fields as if the user had typed them (`input` and `change` events), so React controlled inputs and Vue `v-model` keep the values. A postal code the user has already entered is kept when the chosen street has several codes. If the service does not respond (HTTP 429, 5xx or a network error), the dropdown says it is temporarily unavailable instead of showing an empty result. React / Vue: attach in `useEffect` (React) or `onMounted` (Vue) and call `destroy()` in the effect cleanup or in `onBeforeUnmount`. To write the values into state yourself, use the callbacks: ```jsx useEffect(() => { const widget = window.PTAddress.attach(moradaRef.current, { onSelect: (d) => setForm((f) => ({ ...f, localidade: d.localidade, concelho: d.concelho })), onResolve: (cp7) => setForm((f) => ({ ...f, cp7 })), }); return () => widget.destroy(); }, []); ``` ## Docs - [API documentation](https://moradas.dev/docs): endpoints, parameters, request and response examples, widget and MCP setup - [Quickstart](https://moradas.dev/docs#quickstart): copy-paste examples for /suggest, /resolve and /cp in curl, JavaScript, PHP, Python and C#, with handling of HTTP 429 (Retry-After) and 404 - [Batch check](https://moradas.dev/docs#batch): POST /batch for lists of addresses — verdicts ok / ambiguous / not_found, limits, curl and Python examples - [OpenAPI 3.1 specification](https://moradas.dev/openapi.json): machine-readable description of the REST API ## AI agents (MCP) - [Remote MCP server](https://moradas.dev/mcp): Streamable HTTP, stateless, no key. Tools: `suggest_address`, `resolve_postal_code`, `postal_code_info`. Claude Code: `claude mcp add --transport http moradas https://moradas.dev/mcp`; Cursor and others: `{ "mcpServers": { "moradas": { "url": "https://moradas.dev/mcp" } } }` ## Widget - [widget.js](https://moradas.dev/widget.js): drop-in address autocomplete for any form; fills postal code, locality, municipality and district. No build step, no dependencies. Works with plain HTML, React and Vue. - [Widget setup and options](https://moradas.dev/docs#widget): snippet, options table and React / Vue examples ## Optional - [Live demo](https://moradas.dev/): try the autocomplete in the browser - [Alternative to GeoAPI.pt](https://moradas.dev/alternativa-geoapi): endpoint mapping for projects moving from GeoAPI.pt (in Portuguese) - [Validating a Portuguese postal code](https://moradas.dev/validar-codigo-postal): the CP7 format (`^\d{4}-\d{3}$`), input normalization and an existence check with `/cp` (Portuguese, with an English toggle) - [WooCommerce address autocomplete](https://moradas.dev/autocomplete-morada-woocommerce): a WooCommerce plugin is coming soon to wordpress.org; until then, a widget snippet for the classic checkout (Portuguese, with an English toggle) - [Shopify](https://moradas.dev/integracao-shopify): address autocomplete in the Shopify checkout needs an app (Checkout UI extension, Shopify Plus only); the widget works on theme pages on any plan (Portuguese, with an English toggle) - [PrestaShop](https://moradas.dev/integracao-prestashop): a small two-file module that attaches the widget to the address form (address1, postcode, city) in the checkout and the customer area; full code, not tested on a live store (Portuguese, with an English toggle) - [Magento / Adobe Commerce](https://moradas.dev/integracao-magento): Knockout-rendered checkout fields (MutationObserver) and CSP whitelisting since 2.4.7 (csp_whitelist.xml); full code, not tested on a live store (Portuguese, with an English toggle) - [Wix](https://moradas.dev/integracao-wix): the checkout takes no code; the widget works in your own form inside an Embed HTML element or a Custom Element (Portuguese, with an English toggle) - [Jumpseller](https://moradas.dev/integracao-jumpseller): a script in the theme editor on Checkout Classic (v1); Checkout Standard (v2) allows no scripts (Portuguese, with an English toggle) - [Shopkit](https://moradas.dev/integracao-shopkit): custom JavaScript under Aparência › Avançado attaches the widget to the checkout fields delivery_address / delivery_zip_code / delivery_city; not tested on a live store (Portuguese, with an English toggle) - [Status and changelog](https://moradas.dev/status): live service status, weekly data updates, changelog of the API, widget and plugin, limits and the v1 contract (Portuguese, with an English toggle) - [Alternative to Google Places](https://moradas.dev/alternativa-google-places): comparison with Google Places API (New) for Portuguese addresses, with links to Google's pricing pages (Portuguese, with an English toggle) - [Privacy](https://moradas.dev/privacy): no cookies, no accounts; search text is not stored, only aggregate usage counts and the widget's error reports (in Portuguese, with a summary in English)