# 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 or large locality name (`lisb`, `carcav`) returns that place 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`. If the street text matches nothing but the line carries a valid postal code (`Hotel Tivoli, 1269-050 Lisboa`), the streets of that postal code are returned, each with `by_cp7: true` in `data`. If the text matches nothing and the line carries no full postal code but a standalone 4-digit postal area that exists (`Rua Augusta 100, 1100 Lisboa`, `Rua Desconhecida 12, 4700 Braga`), the street is looked up again without the area, inside it first, and a match comes back as an ordinary street suggestion; if there is still no street, the localities of that area are returned as places (`kind: loc`, `art_id: 0`, `cp7: null`), largest first and a locality named in the line first, each with `by_cp4: true` and `cp4` in `data`. - `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. Every suggestion has `kind`: `street` (a street) or `loc` (a place without a street name). `kind: loc` with `art_id: 0` and `cp7: null` is a whole locality or municipality: never send `art_id` 0 to `/resolve` (404) — show it as a place and let the user keep typing the street. A `loc` with `art_id` > 0 is a small locality with its own postal code (`cp7` set). The label `value` can repeat (three different «Carcavelos» for `carcav`): show `concelho` next to it. 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 Juso","data":{"art_id":126122,"kind":"street","street":"Estrada Malveira da Serra","localidade":"Aldeia de Juso","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. When the postal code and the street are separate fields, attach the widget to the street field and pass the postal-code field as `fill.postal`: the widget writes the CP7 there as soon as it is known; a code the customer had already typed is only replaced once the exact one is known. Do not attach the widget to the postal-code field. 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 - [Integrating without the widget](https://moradas.dev/docs#sem-widget): three `fetch` recipes for your own form — postal code → localidade/concelho/distrito and streets with `/cp/{cp7}` (404 handling, field table), street suggestions with debounce and `kind` handling, door number → exact CP7 with `/resolve` and `segments` - [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` (= /suggest), `resolve_postal_code` (= /resolve), `postal_code_info` (= /cp/{cp7}) and `verify_addresses` (= POST /batch in one call). Claude Code: `claude mcp add --transport http moradas https://moradas.dev/mcp`; Cursor and others: `{ "mcpServers": { "moradas": { "url": "https://moradas.dev/mcp" } } }` - [MCP tools, example call and response](https://moradas.dev/docs#mcp): `verify_addresses` takes `{"items":[{"id":"...","q":"..."}]}` with up to 100 addresses (a CRM or spreadsheet column, an order import) and returns `results` in input order — `id` echoed as sent (`null` when not sent), `q`, `status` `ok` / `ambiguous` / `not_found`, `cp7` and `suggestion`, the same JSON as POST /batch. A call with N items counts as N requests against the rate limit, so send up to 50 per call; more than 100 items or malformed input returns `{"error": "..."}` with the reason, as the other tools do. For a single address use `suggest_address`. ## 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): WooCommerce plugin 0.3.0, published on wordpress.org (https://wordpress.org/plugins/moradas-address-autocomplete/) or to install from a ZIP file — https://moradas.dev/downloads/moradas-address-autocomplete-0.3.0.zip (SHA-256 in the .sha256 file next to it; Plugins › Add New › Upload Plugin › Install Now › Activate); block and classic checkout; WordPress 6.5+, PHP 7.4+, WooCommerce 9.9+ for the block checkout; updates arrive through the WordPress dashboard. Also a widget snippet for the classic checkout (Portuguese, with an English toggle) - [Addresses in CRM, customer portals and back-office](https://moradas.dev/moradas-em-crm-e-area-de-cliente): the widget on sign-up and customer-area forms, /suggest and /resolve in operator forms, POST /batch to check an existing customer database, /cp for postal-code cards (Portuguese, with an English toggle) - [For agencies and freelancers](https://moradas.dev/para-agencias): one address autocomplete for every client store — WooCommerce plugin and PrestaShop module as ZIP files, the two-line widget, the REST API with OpenAPI, the platform guides and the MCP server in one place; what an agency can promise a merchant (stable v1 contract, rate limits, no SLA, privacy, beta), no key or contract per client; contact dev@moradas.dev (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): module 0.2.1 for PrestaShop 1.7.1 to 9 as a ZIP file — https://moradas.dev/downloads/moradas-prestashop-0.2.1.zip (SHA-256 in the .sha256 file next to it; Modules › Module Manager › Upload a module); it loads widget.js and attaches it to the address form (address1, postcode, city) in the checkout and the customer area, only when the country is Portugal; full code on the page, 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)