# Alkan – Accela Seattle Lead Enrichment (`deep_zinc/alkan-accela-enrichment`) Actor

It extracts data from the Seattle building permit owner (Accelea portal) and ingests it into the Alkan platform.

- **URL**: https://apify.com/deep\_zinc/alkan-accela-enrichment.md
- **Developed by:** [Andre Figueroa](https://apify.com/deep_zinc) (community)
- **Categories:** Lead generation, Real estate, News
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $500.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Alkan – Apify Actor: Accela Lead Enrichment

Automatiza el enriquecimiento de leads de construcción de Seattle:
**Portal Accela → Cover Sheet PDF → LLM → Plataforma Alkan**

***

### Arquitectura

```
permit_numbers (input)
       │
       ▼
[PlaywrightCrawler]  ←── Apify Proxy (Residential US)
       │
       ▼  src/accela.js
  cosaccela.seattle.gov
  ├─ Búsqueda por permit_number
  ├─ Apertura de detalle del permiso
  ├─ Pestaña Attachments (AJAX)
  └─ Descarga del cover sheet PDF
       │
       ▼  src/extractor.js
  LLM (Gemini 2.5 Pro ó GPT-4o)
  └─ JSON con owner_name, phone, email, has_gc_assigned, etc.
       │
       ▼  src/ingest.js
  POST /api/public/leads-ingest
  └─ Upsert en tabla `leads` (client_id + permit_number)
```

***

### Variables de entorno del Actor (NO van en el input)

| Variable              | Descripción                                      | Requerida |
|-----------------------|--------------------------------------------------|-----------|
| `LEADS_INGEST_SECRET` | Secreto compartido del endpoint leads-ingest     | ✅        |
| `GEMINI_API_KEY`      | API Key de Google Gemini (preferido para PDFs)   | ✅ (o OPENAI) |
| `OPENAI_API_KEY`      | API Key de OpenAI GPT-4o (alternativa)           | ✅ (o GEMINI) |

***

### Input del Actor

```json
{
  "permit_numbers": ["7127087-DM", "7654321-CN"],
  "client_id": "alkan",
  "app_domain": "app.alkan.io",
  "batch_size": 50
}
```

| Campo            | Tipo     | Descripción                              | Requerido |
|------------------|----------|------------------------------------------|-----------|
| `permit_numbers` | string\[] | Lista de números de permiso de Seattle   | ✅        |
| `client_id`      | string   | Cliente destino en Alkan                 | ✅        |
| `app_domain`     | string   | Dominio de la app (sin https://)         | ✅        |
| `batch_size`     | integer  | Leads por lote de ingesta (default: 50)  | ❌        |

***

### Output

#### Dataset (auditoría por permiso)

Cada permiso genera una entrada:

```json
{
  "permit_number": "7127087-DM",
  "status": "ok",
  "attachment_name": "Signed Application.pdf",
  "llm_data": { "owner_name": "BITC 58 LLC", "has_gc_assigned": false, ... },
  "lead_payload": { ... },
  "timestamp": "2026-05-30T..."
}
```

**Valores de `status`:**

- `ok` — enriquecido y enviado a Alkan
- `not_found` — permiso no encontrado en Accela
- `no_attachment` — no tiene documentos adjuntos
- `no_attachment_tab` — no se encontró la pestaña de Attachments
- `download_error` — error al descargar el PDF
- `error` — error inesperado
- `failed_all_retries` — falló todos los reintentos

#### Entrega de resultados a clientes

Todo usuario que ejecute este Actor recibe automáticamente sus resultados en el **Dataset** de esa ejecución (vía Apify), descargable en JSON, CSV o Excel, accesible por API, o entregable por webhook al finalizar el run. Esta es la entrega estándar y no requiere configuración adicional.

La sincronización directa a la tabla `leads` de Alkan (`POST /api/public/leads-ingest`) es un beneficio adicional, exclusivo para organizaciones que ya cuentan con una instancia de Alkan configurada con `client_id` y `app_domain` propios. Si el cliente no usa la plataforma Alkan, sus leads seguirán disponibles normalmente en el Dataset de Apify.

#### KeyValueStore

Los PDFs descargados se guardan con key `pdf_{permitNumber}` para re-extracción sin re-scraping.

***

### Instalación local (desarrollo)

```bash
npm install
## Configurar .env con tus keys
cp .env.example .env

## Probar con un permiso real
node src/test.js
```

***

### Configuraciones de Apify recomendadas

- **Memory:** 2048 MB (Playwright necesita memoria)
- **Timeout:** 3600 segundos para lotes grandes
- **Proxy:** Residential US (incluido en el código)
- **Max concurrency:** 2 (configurado en el código, no subir sin probar)

***

### Criterios de aceptación (Sección 8 del brief)

- \[x] Dado un permit\_number con cover sheet → descarga en < 30s
- \[x] LLM devuelve JSON válido con owner\_name y has\_gc\_assigned
- \[x] POST a leads-ingest → 200 OK, lead enriquecido sin duplicarse
- \[x] Permiso sin attachments → status `no_attachment`, run continúa
- \[x] 20 permisos seguidos sin bloqueo (delays 3–6s configurados)

***

### Notas de implementación

#### Por qué PlaywrightCrawler y no Cheerio

Accela ACA es una app WebForms ASP.NET con postbacks AJAX y ViewState.
Sin JS real, el portal no funciona.

#### Disclaimer de Accela

La función `handleDisclaimer()` en `accela.js` detecta y acepta automáticamente
la pantalla de términos que Accela muestra en la primera visita de sesión.

#### PDFs grandes

Si el PDF supera 10 MB, el extractor trunca al principio del archivo.
El owner block siempre aparece en las primeras páginas del documento.

#### Deep-links vs búsqueda

El brief recomienda verificar si el deep-link `CapDetail.aspx?capID1=...` funciona.
Esta implementación usa siempre la búsqueda por número de permiso (más robusta),
que es la ruta recomendada en la Sección 3.

***

*ALKAN · Andrés Alcántara · alkanassistant@gmail.com · Mayo 2026*

# Actor input Schema

## `permit_numbers` (type: `array`):

Lista de permit numbers de Seattle

## `client_id` (type: `string`):

Identificador del cliente en Alkan (ej. alkan)

## `app_domain` (type: `string`):

Dominio de la plataforma Alkan (ej. app.alkan.io)

## `batch_size` (type: `integer`):

Cuantos leads enviar por POST (max 500)

## Actor input object example

```json
{
  "client_id": "alkan",
  "batch_size": 50
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("deep_zinc/alkan-accela-enrichment").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("deep_zinc/alkan-accela-enrichment").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call deep_zinc/alkan-accela-enrichment --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=deep_zinc/alkan-accela-enrichment",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/mbyWbXzahJnDWasbB/builds/H0Jsnnh7tA5T2gSz3/openapi.json
