Skip to content
SDK · Go

PropRaven Go SDK

Typed client for the PropRaven API, generated from its OpenAPI 3.1 spec. Standard library only, Go 1.22+. Go has no central registry: tags in the repo are the releases.

The current release was generated from OpenAPI 1.2.0 and wraps 70 of the 88 operations in the live spec (version 1.2.1). These 18 operations are not in the SDK yet; call them over REST with the same API key:

  • GET /api/v1/data/databricks/grant
  • GET /api/v1/intelligence/runs/{runId}
  • GET /api/v1/intelligence/runs/{runId}/handoff
  • GET /api/v1/licensees/firms
  • GET /api/v1/licensees/individuals
  • GET /api/v1/market/zillow/compare
  • GET /api/v1/market/zillow/context
  • GET /api/v1/market/zillow/timeseries
  • GET /api/v1/neighborhood/distress
  • GET /api/v1/parcels/{id}/distress
  • GET /api/v1/parcels/{id}/signals
  • GET /api/v1/parcels/{id}/tax-status
  • GET /api/v1/parcels/{id}/zoning
  • GET /api/v1/permits/{permitId}/events
  • GET /api/v1/r2-credentials
  • GET /api/v1/zoning/coverage
  • GET /api/v1/zoning/search
  • POST /api/v1/intelligence/scenarios

Install

go get github.com/jdw2111/propraven-go@latest

Quickstart

package main

import (
    "context"
    "fmt"
    "log"

    propraven "github.com/jdw2111/propraven-go"
)

func main() {
    client := propraven.NewClient() // reads PROPRAVEN_API_KEY
    ctx := context.Background()

    parcel, err := client.Parcels.Get(ctx, "37:119:12104406", nil)
    if err != nil {
        log.Fatal(err)
    }
    // Nullable fields are pointers: nil when the record has no owner or no value.
    owner, value := "(no owner of record)", "(no assessed value)"
    if parcel.OwnerName != nil {
        owner = *parcel.OwnerName
    }
    if parcel.TotalAssessedValue != nil {
        value = fmt.Sprint(*parcel.TotalAssessedValue)
    }
    fmt.Println(owner, value)

    permits, err := client.Parcels.Permits(ctx, "37:119:12104406",
        &propraven.ParcelsPermitsParams{Shape: propraven.String("envelope")})
    if err != nil {
        log.Fatal(err)
    }
    if permits.Object != nil {
        fmt.Println(permits.Object.PermitCount, "permits")
    }
}

Every nullable field is a pointer (for example OwnerName *string, TotalAssessedValue); check it for nil before dereferencing, since parcels with no owner or no assessed value are common.

Options: propraven.WithAPIKey, WithBaseURL, WithHTTPClient, WithMaxRetries, WithTimeout, WithHeader; any option can also be passed to a single call.

The default base URL is https://propraven.com (every path starts with /api/v1); override it withPROPRAVEN_BASE_URL. Parcel ids take the canonicalstate:county:parcel form, the legacy 5-digit form or a PropRaven UUID.

Errors

The API answers every error with an RFC 7807 problem body; the SDK raises a typed error per status (400, 401, 402, 403, 404, 405, 409, 413, 422, 429, 503, 504, other 5xx) carryingcode, detail, the per-parameter errors and the request id.

_, err := client.Parcels.Get(ctx, "37:119:00000000000", nil)
switch {
case propraven.IsNotFound(err):
    // 404
case propraven.IsRateLimited(err):
    // 429; see err.(*propraven.Error).RetryAfter
case propraven.IsPaymentRequired(err):
    // 402; the x402 offer is in .Accepts
}
var apiErr *propraven.Error
if errors.As(err, &apiErr) {
    fmt.Println(apiErr.StatusCode, apiErr.Code, apiErr.Detail, apiErr.RequestID)
}

Retries, timeouts and rate limits

Network errors, 429, 503 and 504 are retried twice by default (other 5xx only for GET and DELETE), waiting for Retry-After or the rate-limit reset when the server names one, otherwise backing off exponentially. A wait longer than 60 seconds is raised instead of slept, and a 402 is never retried.

client := propraven.NewClient(
    propraven.WithMaxRetries(3),
    propraven.WithTimeout(30*time.Second),
)
fmt.Println(client.LastRateLimit())

Pagination

it := client.Deals.AbsenteeIter(ctx,
    &propraven.DealsAbsenteeParams{CountyFIPS: propraven.String("37119")},
    propraven.IterOptions{PageSize: 100, MaxItems: 1000})
for it.Next() {
    row := it.Current()
    fmt.Println(row.ParcelID)
}
if err := it.Err(); err != nil {
    log.Fatal(err)
}

Webhook signature verification

Checks X-PropRaven-Signature (t=<unix_ms>,v1=<hex>, HMAC-SHA256 overt.raw_body) in constant time with a 5-minute replay window. A dependency-free verifier is on thewebhooks page.

import (
    "io"
    "net/http"
    "os"

    propraven "github.com/jdw2111/propraven-go"
)

func handler(w http.ResponseWriter, r *http.Request) {
    rawBody, _ := io.ReadAll(r.Body) // the exact request body, before any JSON parsing
    event, err := propraven.VerifyWebhook(rawBody,
        r.Header.Get("X-PropRaven-Signature"), os.Getenv("PROPRAVEN_WEBHOOK_SECRET"))
    if err != nil {
        http.Error(w, "invalid signature", http.StatusUnauthorized)
        return
    }
    _ = event // json.RawMessage: unmarshal into your own struct; the name is in event_type
    w.WriteHeader(http.StatusNoContent)
}

Resources