iptuapi 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21,166 +21,513 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AuthenticationError: () => AuthenticationError,
24
+ CidadeEnum: () => CidadeEnum,
24
25
  ForbiddenError: () => ForbiddenError,
25
26
  IPTUAPIError: () => IPTUAPIError,
26
27
  IPTUClient: () => IPTUClient,
28
+ NetworkError: () => NetworkError,
27
29
  NotFoundError: () => NotFoundError,
28
30
  RateLimitError: () => RateLimitError,
31
+ ServerError: () => ServerError,
32
+ TimeoutError: () => TimeoutError,
33
+ ValidationError: () => ValidationError,
29
34
  default: () => index_default
30
35
  });
31
36
  module.exports = __toCommonJS(index_exports);
32
- var IPTUAPIError = class extends Error {
33
- constructor(message, statusCode) {
37
+ var CidadeEnum = {
38
+ SAO_PAULO: "sp",
39
+ BELO_HORIZONTE: "bh",
40
+ RECIFE: "recife"
41
+ };
42
+ var IPTUAPIError = class _IPTUAPIError extends Error {
43
+ statusCode;
44
+ requestId;
45
+ responseBody;
46
+ constructor(message, statusCode, requestId, responseBody) {
34
47
  super(message);
35
- this.statusCode = statusCode;
36
48
  this.name = "IPTUAPIError";
49
+ this.statusCode = statusCode;
50
+ this.requestId = requestId;
51
+ this.responseBody = responseBody;
52
+ Object.setPrototypeOf(this, _IPTUAPIError.prototype);
53
+ }
54
+ get isRetryable() {
55
+ return this.statusCode ? [429, 500, 502, 503, 504].includes(this.statusCode) : false;
37
56
  }
38
57
  };
39
- var AuthenticationError = class extends IPTUAPIError {
40
- constructor(message = "API Key inv\xE1lida ou expirada") {
41
- super(message, 401);
58
+ var AuthenticationError = class _AuthenticationError extends IPTUAPIError {
59
+ constructor(message = "API Key inv\xE1lida ou expirada", requestId, responseBody) {
60
+ super(message, 401, requestId, responseBody);
42
61
  this.name = "AuthenticationError";
62
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
43
63
  }
44
64
  };
45
- var RateLimitError = class extends IPTUAPIError {
46
- constructor(message = "Limite de requisi\xE7\xF5es excedido") {
47
- super(message, 429);
48
- this.name = "RateLimitError";
65
+ var ForbiddenError = class _ForbiddenError extends IPTUAPIError {
66
+ requiredPlan;
67
+ constructor(message = "Plano n\xE3o autorizado para este recurso", requiredPlan, requestId, responseBody) {
68
+ super(message, 403, requestId, responseBody);
69
+ this.name = "ForbiddenError";
70
+ this.requiredPlan = requiredPlan;
71
+ Object.setPrototypeOf(this, _ForbiddenError.prototype);
49
72
  }
50
73
  };
51
- var NotFoundError = class extends IPTUAPIError {
52
- constructor(message = "Recurso n\xE3o encontrado") {
53
- super(message, 404);
74
+ var NotFoundError = class _NotFoundError extends IPTUAPIError {
75
+ constructor(message = "Recurso n\xE3o encontrado", requestId, responseBody) {
76
+ super(message, 404, requestId, responseBody);
54
77
  this.name = "NotFoundError";
78
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
55
79
  }
56
80
  };
57
- var ForbiddenError = class extends IPTUAPIError {
58
- constructor(message = "Plano n\xE3o autorizado para este recurso") {
59
- super(message, 403);
60
- this.name = "ForbiddenError";
81
+ var RateLimitError = class _RateLimitError extends IPTUAPIError {
82
+ retryAfter;
83
+ limit;
84
+ remaining;
85
+ constructor(message = "Limite de requisi\xE7\xF5es excedido", retryAfter, limit, remaining, requestId, responseBody) {
86
+ super(message, 429, requestId, responseBody);
87
+ this.name = "RateLimitError";
88
+ this.retryAfter = retryAfter;
89
+ this.limit = limit;
90
+ this.remaining = remaining;
91
+ Object.setPrototypeOf(this, _RateLimitError.prototype);
92
+ }
93
+ get isRetryable() {
94
+ return true;
95
+ }
96
+ };
97
+ var ValidationError = class _ValidationError extends IPTUAPIError {
98
+ errors;
99
+ constructor(message = "Par\xE2metros inv\xE1lidos", errors, requestId, responseBody) {
100
+ super(message, 400, requestId, responseBody);
101
+ this.name = "ValidationError";
102
+ this.errors = errors;
103
+ Object.setPrototypeOf(this, _ValidationError.prototype);
61
104
  }
62
105
  };
106
+ var ServerError = class _ServerError extends IPTUAPIError {
107
+ constructor(message = "Erro interno do servidor", statusCode = 500, requestId, responseBody) {
108
+ super(message, statusCode, requestId, responseBody);
109
+ this.name = "ServerError";
110
+ Object.setPrototypeOf(this, _ServerError.prototype);
111
+ }
112
+ get isRetryable() {
113
+ return true;
114
+ }
115
+ };
116
+ var TimeoutError = class _TimeoutError extends IPTUAPIError {
117
+ timeoutMs;
118
+ constructor(message = "Timeout na requisi\xE7\xE3o", timeoutMs) {
119
+ super(message, 408);
120
+ this.name = "TimeoutError";
121
+ this.timeoutMs = timeoutMs;
122
+ Object.setPrototypeOf(this, _TimeoutError.prototype);
123
+ }
124
+ get isRetryable() {
125
+ return true;
126
+ }
127
+ };
128
+ var NetworkError = class _NetworkError extends IPTUAPIError {
129
+ originalError;
130
+ constructor(message = "Erro de conex\xE3o com a API", originalError) {
131
+ super(message);
132
+ this.name = "NetworkError";
133
+ this.originalError = originalError;
134
+ Object.setPrototypeOf(this, _NetworkError.prototype);
135
+ }
136
+ get isRetryable() {
137
+ return true;
138
+ }
139
+ };
140
+ var DEFAULT_RETRY_CONFIG = {
141
+ maxRetries: 3,
142
+ initialDelay: 500,
143
+ maxDelay: 1e4,
144
+ backoffFactor: 2,
145
+ retryableStatuses: [429, 500, 502, 503, 504]
146
+ };
63
147
  var IPTUClient = class {
64
148
  apiKey;
65
149
  baseUrl;
66
150
  timeout;
151
+ retryConfig;
152
+ logger;
153
+ logRequests;
154
+ logResponses;
155
+ userAgent;
156
+ _rateLimit;
157
+ _lastRequestId;
67
158
  constructor(apiKey, options = {}) {
159
+ if (!apiKey) {
160
+ throw new Error("API Key \xE9 obrigat\xF3ria");
161
+ }
68
162
  this.apiKey = apiKey;
69
163
  this.baseUrl = options.baseUrl || "https://iptuapi.com.br/api/v1";
70
164
  this.timeout = options.timeout || 3e4;
165
+ this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...options.retry };
166
+ this.logger = options.logger;
167
+ this.logRequests = options.logRequests || false;
168
+ this.logResponses = options.logResponses || false;
169
+ this.userAgent = options.userAgent || "iptuapi-js/2.0.0";
170
+ }
171
+ // ===========================================================================
172
+ // Properties
173
+ // ===========================================================================
174
+ /** Rate limit info from last request */
175
+ get rateLimit() {
176
+ return this._rateLimit;
177
+ }
178
+ /** Request ID from last request (useful for support) */
179
+ get lastRequestId() {
180
+ return this._lastRequestId;
181
+ }
182
+ // ===========================================================================
183
+ // Private Methods
184
+ // ===========================================================================
185
+ log(level, message, ...args) {
186
+ if (this.logger && this.logger[level]) {
187
+ this.logger[level](message, ...args);
188
+ }
189
+ }
190
+ sleep(ms) {
191
+ return new Promise((resolve) => setTimeout(resolve, ms));
192
+ }
193
+ calculateDelay(attempt) {
194
+ const delay = this.retryConfig.initialDelay * Math.pow(this.retryConfig.backoffFactor, attempt);
195
+ return Math.min(delay, this.retryConfig.maxDelay);
196
+ }
197
+ extractRateLimit(headers) {
198
+ const limit = headers.get("X-RateLimit-Limit");
199
+ const remaining = headers.get("X-RateLimit-Remaining");
200
+ const reset = headers.get("X-RateLimit-Reset");
201
+ if (limit && remaining && reset) {
202
+ const resetTimestamp = parseInt(reset, 10);
203
+ return {
204
+ limit: parseInt(limit, 10),
205
+ remaining: parseInt(remaining, 10),
206
+ reset: resetTimestamp,
207
+ resetDate: new Date(resetTimestamp * 1e3)
208
+ };
209
+ }
210
+ return void 0;
211
+ }
212
+ async handleErrorResponse(response, requestId) {
213
+ let body = {};
214
+ try {
215
+ body = await response.json();
216
+ } catch {
217
+ body = { detail: response.statusText };
218
+ }
219
+ const message = body.detail || `HTTP ${response.status}`;
220
+ switch (response.status) {
221
+ case 400:
222
+ case 422:
223
+ throw new ValidationError(
224
+ message,
225
+ body.errors,
226
+ requestId,
227
+ body
228
+ );
229
+ case 401:
230
+ throw new AuthenticationError(message, requestId, body);
231
+ case 403:
232
+ throw new ForbiddenError(
233
+ message,
234
+ body.required_plan,
235
+ requestId,
236
+ body
237
+ );
238
+ case 404:
239
+ throw new NotFoundError(message, requestId, body);
240
+ case 429:
241
+ const retryAfter = response.headers.get("Retry-After");
242
+ throw new RateLimitError(
243
+ message,
244
+ retryAfter ? parseInt(retryAfter, 10) : void 0,
245
+ this._rateLimit?.limit,
246
+ this._rateLimit?.remaining,
247
+ requestId,
248
+ body
249
+ );
250
+ case 500:
251
+ case 502:
252
+ case 503:
253
+ case 504:
254
+ throw new ServerError(message, response.status, requestId, body);
255
+ default:
256
+ throw new IPTUAPIError(message, response.status, requestId, body);
257
+ }
71
258
  }
72
259
  async request(method, endpoint, params, body) {
73
260
  const url = new URL(`${this.baseUrl}${endpoint}`);
74
261
  if (params) {
75
262
  Object.entries(params).forEach(([key, value]) => {
76
- if (value) url.searchParams.append(key, value);
263
+ if (value !== void 0 && value !== null && value !== "") {
264
+ url.searchParams.append(key, String(value));
265
+ }
77
266
  });
78
267
  }
79
- const controller = new AbortController();
80
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
81
- try {
82
- const response = await fetch(url.toString(), {
83
- method,
84
- headers: {
85
- "X-API-Key": this.apiKey,
86
- "Content-Type": "application/json"
87
- },
88
- body: body ? JSON.stringify(body) : void 0,
89
- signal: controller.signal
90
- });
91
- clearTimeout(timeoutId);
92
- if (response.ok) {
93
- return response.json();
94
- }
95
- switch (response.status) {
96
- case 401:
97
- throw new AuthenticationError();
98
- case 403:
99
- throw new ForbiddenError();
100
- case 404:
101
- throw new NotFoundError();
102
- case 429:
103
- throw new RateLimitError();
104
- default:
105
- const errorData = await response.json().catch(() => ({}));
106
- throw new IPTUAPIError(
107
- errorData.detail || `Erro na API: ${response.statusText}`,
108
- response.status
268
+ const headers = {
269
+ "X-API-Key": this.apiKey,
270
+ "Content-Type": "application/json",
271
+ Accept: "application/json",
272
+ "User-Agent": this.userAgent
273
+ };
274
+ let lastError;
275
+ let attempt = 0;
276
+ while (attempt <= this.retryConfig.maxRetries) {
277
+ const controller = new AbortController();
278
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
279
+ try {
280
+ if (this.logRequests) {
281
+ this.log(
282
+ "debug",
283
+ `Request: ${method} ${url}`,
284
+ params ? { params } : {},
285
+ body ? { body } : {}
109
286
  );
287
+ }
288
+ const startTime = Date.now();
289
+ const response = await fetch(url.toString(), {
290
+ method,
291
+ headers,
292
+ body: body ? JSON.stringify(body) : void 0,
293
+ signal: controller.signal
294
+ });
295
+ clearTimeout(timeoutId);
296
+ const elapsedMs = Date.now() - startTime;
297
+ this._rateLimit = this.extractRateLimit(response.headers);
298
+ this._lastRequestId = response.headers.get("X-Request-ID") || void 0;
299
+ if (this.logResponses) {
300
+ this.log(
301
+ "debug",
302
+ `Response: ${response.status} ${url} (${elapsedMs}ms)`
303
+ );
304
+ }
305
+ if (response.ok) {
306
+ return await response.json();
307
+ }
308
+ if (this.retryConfig.retryableStatuses.includes(response.status) && attempt < this.retryConfig.maxRetries) {
309
+ const delay = this.calculateDelay(attempt);
310
+ this.log(
311
+ "warn",
312
+ `Request failed with ${response.status}, retrying in ${delay}ms (attempt ${attempt + 1}/${this.retryConfig.maxRetries})`
313
+ );
314
+ await this.sleep(delay);
315
+ attempt++;
316
+ continue;
317
+ }
318
+ await this.handleErrorResponse(response, this._lastRequestId);
319
+ } catch (error) {
320
+ clearTimeout(timeoutId);
321
+ if (error instanceof IPTUAPIError) {
322
+ throw error;
323
+ }
324
+ if (error instanceof Error) {
325
+ if (error.name === "AbortError") {
326
+ lastError = new TimeoutError(
327
+ `Timeout ap\xF3s ${this.timeout}ms`,
328
+ this.timeout
329
+ );
330
+ } else if (error.message.includes("fetch") || error.message.includes("network")) {
331
+ lastError = new NetworkError(
332
+ `Erro de conex\xE3o: ${error.message}`,
333
+ error
334
+ );
335
+ } else {
336
+ lastError = error;
337
+ }
338
+ if (attempt < this.retryConfig.maxRetries) {
339
+ const delay = this.calculateDelay(attempt);
340
+ this.log(
341
+ "warn",
342
+ `Request failed: ${error.message}, retrying in ${delay}ms (attempt ${attempt + 1}/${this.retryConfig.maxRetries})`
343
+ );
344
+ await this.sleep(delay);
345
+ attempt++;
346
+ continue;
347
+ }
348
+ }
349
+ throw lastError || error;
110
350
  }
111
- } catch (error) {
112
- clearTimeout(timeoutId);
113
- if (error instanceof IPTUAPIError) throw error;
114
- if (error instanceof Error && error.name === "AbortError") {
115
- throw new IPTUAPIError("Timeout na requisi\xE7\xE3o", 408);
116
- }
117
- throw error;
118
351
  }
352
+ throw lastError || new IPTUAPIError("Max retries exceeded");
353
+ }
354
+ async consultaEndereco(paramsOrLogradouro, numero, cidade) {
355
+ let params;
356
+ if (typeof paramsOrLogradouro === "string") {
357
+ params = {
358
+ logradouro: paramsOrLogradouro,
359
+ numero,
360
+ cidade: cidade || "sp"
361
+ };
362
+ } else {
363
+ params = {
364
+ logradouro: paramsOrLogradouro.logradouro,
365
+ numero: paramsOrLogradouro.numero,
366
+ complemento: paramsOrLogradouro.complemento,
367
+ cidade: paramsOrLogradouro.cidade || "sp",
368
+ incluir_historico: paramsOrLogradouro.incluirHistorico,
369
+ incluir_comparaveis: paramsOrLogradouro.incluirComparaveis,
370
+ incluir_zoneamento: paramsOrLogradouro.incluirZoneamento
371
+ };
372
+ }
373
+ return this.request(
374
+ "GET",
375
+ "/consulta/endereco",
376
+ params
377
+ );
119
378
  }
120
379
  /**
121
- * Busca dados de IPTU por endereço
380
+ * Busca dados de IPTU por número SQL (contribuinte).
381
+ *
382
+ * @param sql - Número SQL do imóvel
383
+ * @param cidade - Cidade da consulta
384
+ * @param options - Opções adicionais
385
+ * @returns Dados completos do imóvel
122
386
  */
123
- async consultaEndereco(logradouro, numero) {
124
- return this.request("GET", "/consulta/endereco", {
125
- logradouro,
126
- numero: numero || ""
387
+ async consultaSQL(sql, cidade = "sp", options) {
388
+ return this.request("GET", `/consulta/sql/${sql}`, {
389
+ cidade,
390
+ incluir_historico: options?.incluirHistorico,
391
+ incluir_comparaveis: options?.incluirComparaveis
127
392
  });
128
393
  }
129
394
  /**
130
- * Busca dados de IPTU por número SQL (Starter+)
395
+ * Busca imóveis por CEP.
396
+ *
397
+ * @param cep - CEP do imóvel
398
+ * @param cidade - Cidade da consulta
399
+ * @returns Lista de imóveis no CEP
131
400
  */
132
- async consultaSQL(sql) {
133
- return this.request("GET", "/consulta/sql", { sql });
401
+ async consultaCEP(cep, cidade = "sp") {
402
+ const cleanCep = cep.replace(/\D/g, "");
403
+ return this.request(
404
+ "GET",
405
+ `/consulta/cep/${cleanCep}`,
406
+ { cidade }
407
+ );
134
408
  }
135
409
  /**
136
- * Busca dados de IPTU por endereço para qualquer cidade suportada
137
- * @param cidade - Cidade ("sao_paulo", "belo_horizonte" ou "recife")
138
- * @param logradouro - Nome da rua/avenida
139
- * @param numero - Número do imóvel (opcional)
140
- * @param ano - Ano de referência (default: 2025)
141
- * @param limit - Limite de resultados (default: 20)
410
+ * Consulta zoneamento por coordenadas.
411
+ *
412
+ * @param latitude - Latitude do ponto
413
+ * @param longitude - Longitude do ponto
414
+ * @returns Dados de zoneamento
142
415
  */
143
- async consultaIPTU(cidade, logradouro, numero, ano = 2025, limit = 20) {
144
- const params = {
145
- logradouro,
146
- ano: ano.toString(),
147
- limit: limit.toString()
148
- };
149
- if (numero !== void 0) {
150
- params.numero = numero.toString();
151
- }
416
+ async consultaZoneamento(latitude, longitude) {
417
+ return this.request("GET", "/consulta/zoneamento", {
418
+ latitude,
419
+ longitude
420
+ });
421
+ }
422
+ // ===========================================================================
423
+ // Valuation Endpoints (Pro+)
424
+ // ===========================================================================
425
+ /**
426
+ * Estima o valor de mercado do imóvel usando ML.
427
+ * Disponível apenas para planos Pro e Enterprise.
428
+ *
429
+ * @param params - Parâmetros do imóvel
430
+ * @returns Estimativa de valor
431
+ * @throws {ForbiddenError} Se o plano não permitir
432
+ */
433
+ async valuationEstimate(params) {
434
+ return this.request(
435
+ "POST",
436
+ "/valuation/estimate",
437
+ void 0,
438
+ {
439
+ area_terreno: params.area_terreno,
440
+ area_construida: params.area_construida,
441
+ bairro: params.bairro,
442
+ zona: params.zona,
443
+ tipo_uso: params.tipo_uso,
444
+ tipo_padrao: params.tipo_padrao,
445
+ ano_construcao: params.ano_construcao,
446
+ cidade: params.cidade || "sp"
447
+ }
448
+ );
449
+ }
450
+ /**
451
+ * Valuation em lote (até 100 imóveis).
452
+ * Disponível apenas para plano Enterprise.
453
+ *
454
+ * @param imoveis - Lista de imóveis para avaliar
455
+ * @returns Resultados de valuation para cada imóvel
456
+ */
457
+ async valuationBatch(imoveis) {
458
+ return this.request(
459
+ "POST",
460
+ "/valuation/estimate/batch",
461
+ void 0,
462
+ { imoveis }
463
+ );
464
+ }
465
+ /**
466
+ * Busca imóveis comparáveis para análise.
467
+ *
468
+ * @param bairro - Nome do bairro
469
+ * @param areaMin - Área mínima em m²
470
+ * @param areaMax - Área máxima em m²
471
+ * @param options - Opções adicionais
472
+ * @returns Lista de imóveis comparáveis
473
+ */
474
+ async valuationComparables(bairro, areaMin, areaMax, options) {
475
+ return this.request("GET", "/valuation/comparables", {
476
+ bairro,
477
+ area_min: areaMin,
478
+ area_max: areaMax,
479
+ tipo_uso: options?.tipoUso,
480
+ cidade: options?.cidade || "sp",
481
+ limit: options?.limit || 10
482
+ });
483
+ }
484
+ // ===========================================================================
485
+ // Dados Endpoints
486
+ // ===========================================================================
487
+ /**
488
+ * Histórico de valores IPTU de um imóvel.
489
+ *
490
+ * @param sql - Número SQL do imóvel
491
+ * @param cidade - Cidade da consulta
492
+ * @returns Lista com histórico anual
493
+ */
494
+ async dadosIPTUHistorico(sql, cidade = "sp") {
152
495
  return this.request(
153
496
  "GET",
154
- `/dados/iptu/${cidade}/endereco`,
155
- params
497
+ `/dados/iptu/historico/${sql}`,
498
+ { cidade }
156
499
  );
157
500
  }
158
501
  /**
159
- * Busca dados de IPTU pelo identificador único do imóvel
160
- * @param cidade - Cidade ("sao_paulo", "belo_horizonte" ou "recife")
161
- * @param identificador - Número SQL (SP), Índice Cadastral (BH) ou Contribuinte (Recife)
162
- * @param ano - Ano de referência (opcional)
502
+ * Consulta dados de empresa por CNPJ.
503
+ *
504
+ * @param cnpj - CNPJ da empresa
505
+ * @returns Dados cadastrais
163
506
  */
164
- async consultaIPTUSQL(cidade, identificador, ano) {
165
- const params = {};
166
- if (ano !== void 0) {
167
- params.ano = ano.toString();
168
- }
507
+ async dadosCNPJ(cnpj) {
508
+ const cleanCnpj = cnpj.replace(/\D/g, "");
169
509
  return this.request(
170
510
  "GET",
171
- `/dados/iptu/${cidade}/sql/${encodeURIComponent(identificador)}`,
172
- params
511
+ `/dados/cnpj/${cleanCnpj}`
173
512
  );
174
513
  }
175
514
  /**
176
- * Estima o valor de mercado do imóvel (Pro+)
515
+ * Correção monetária pelo IPCA.
516
+ *
517
+ * @param valor - Valor a corrigir
518
+ * @param dataOrigem - Data do valor original (YYYY-MM)
519
+ * @param dataDestino - Data destino (default: atual)
520
+ * @returns Valor corrigido e fator de correção
177
521
  */
178
- async valuationEstimate(params) {
522
+ async dadosIPCACorrigir(valor, dataOrigem, dataDestino) {
179
523
  return this.request(
180
- "POST",
181
- "/valuation/estimate",
182
- void 0,
183
- params
524
+ "GET",
525
+ "/dados/ipca/corrigir",
526
+ {
527
+ valor,
528
+ data_origem: dataOrigem,
529
+ data_destino: dataDestino
530
+ }
184
531
  );
185
532
  }
186
533
  };
@@ -188,9 +535,14 @@ var index_default = IPTUClient;
188
535
  // Annotate the CommonJS export names for ESM import in node:
189
536
  0 && (module.exports = {
190
537
  AuthenticationError,
538
+ CidadeEnum,
191
539
  ForbiddenError,
192
540
  IPTUAPIError,
193
541
  IPTUClient,
542
+ NetworkError,
194
543
  NotFoundError,
195
- RateLimitError
544
+ RateLimitError,
545
+ ServerError,
546
+ TimeoutError,
547
+ ValidationError
196
548
  });