DocsBatch Geocoding

Batch Geocoding

This is Pattern 1: server-side integration — your job or CLI holds the API key and calls PickPoint directly via the SDK. No REST wrapper needed. See the Integration guide for the full picture.

At ~100 ms per request, geocoding 10,000 addresses sequentially takes over 15 minutes. The official SDK batch helpers keep up to 20 requests in flight at all times — when one finishes, the next starts immediately (a conveyor, not fixed waves of 20). That cuts 10,000 addresses to about a minute.

Pass the full list of queries to the SDK — it manages concurrency, retries on transient errors, and auth refresh. Results are returned in the same order as the input. A soft 4xx for one address (e.g. no match) becomes an empty slot — the batch continues.
LanguageBatch API
JavaScriptpp.forward(queries[]) — pass an array of query objects
Pythonawait pp.forward_batch(queries[])
Gopp.ForwardBatch(ctx, queries[])
Rustpp.forward_batch(queries).await
// batch_geocode.go
// go mod init yourapp && go get github.com/pickpoint/go-sdk
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"

	"github.com/pickpoint/go-sdk/pickpoint"
)

func main() {
	pp, err := pickpoint.New(pickpoint.Config{APIKey: os.Getenv("PICKPOINT_KEY")})
	if err != nil {
		panic(err)
	}

	addresses := []string{"Eiffel Tower, Paris", "Big Ben, London", "Colosseum, Rome"}
	queries := make([]pickpoint.Query, len(addresses))
	for i, addr := range addresses {
		queries[i] = pickpoint.Query{"q": addr, "limit": "1"}
	}

	// ForwardBatch — conveyor keeps up to 20 requests in flight
	out, err := pp.ForwardBatch(context.Background(), queries)
	if err != nil {
		panic(err)
	}

	for i, addr := range addresses {
		if len(out[i]) == 0 {
			fmt.Fprintf(os.Stderr, "no result: %s\n", addr)
			continue
		}
		b, _ := json.Marshal(out[i][0])
		fmt.Printf("[%d] %s → %s\n", i, addr, b)
	}
}
// batch-geocode.mjs
// npm install @pickpoint/sdk
import { writeFileSync } from 'fs';
import { PickPoint } from '@pickpoint/sdk';

const pp = new PickPoint({ apiKey: process.env.PICKPOINT_KEY });

const addresses = ['Eiffel Tower, Paris', 'Big Ben, London', 'Colosseum, Rome'];

// Pass an array — SDK keeps up to 20 requests in flight (conveyor, not fixed waves)
const queries = addresses.map(q => ({ q, limit: '1' }));
const results = await pp.forward(queries);

writeFileSync('geocoded.json', JSON.stringify(
  addresses.map((address, i) => ({ address, result: results[i]?.[0] ?? null })),
  null, 2,
));

console.log(`Done — ${results.filter(r => r?.length).length}/${addresses.length}`);
# batch_geocode.py
# pip install pickpoint
import asyncio
import json
import os

from pickpoint import Client, Config

ADDRESSES = ["Eiffel Tower, Paris", "Big Ben, London", "Colosseum, Rome"]

async def main() -> None:
    async with Client(Config(api_key=os.environ["PICKPOINT_KEY"])) as pp:
        queries = [{"q": addr, "limit": "1"} for addr in ADDRESSES]
        # forward_batch — conveyor keeps up to 20 requests in flight
        results = await pp.forward_batch(queries)

    rows = [{"address": a, "result": r[0] if r else None}
            for a, r in zip(ADDRESSES, results)]
    print(json.dumps(rows, indent=2))

if __name__ == "__main__":
    asyncio.run(main())
// Cargo.toml: pickpoint = "2", tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

use pickpoint::{query, Client, Config};

#[tokio::main]
async fn main() -> pickpoint::Result<()> {
    let pp = Client::new(Config::with_api_key(
        std::env::var("PICKPOINT_KEY").expect("PICKPOINT_KEY"),
    ))
    .await?;

    let addresses = vec!["Eiffel Tower, Paris", "Big Ben, London", "Colosseum, Rome"];
    let queries: Vec<_> = addresses
        .iter()
        .map(|a| query([("q", *a), ("limit", "1")]))
        .collect();

    // forward_batch — conveyor keeps up to 20 requests in flight
    let results = pp.forward_batch(queries).await?;

    for (addr, slot) in addresses.iter().zip(&results) {
        match slot.first() {
            Some(v) => println!("{} → {}", addr, v),
            None => eprintln!("no result: {}", addr),
        }
    }
    Ok(())
}

Throughput

With the SDK conveyor at 20 in-flight requests and ~100 ms average latency: ~200 req/sec, ~720k/hour.

AddressesEstimated time
1,000~5 seconds
10,000~1 minute
100,000~8–10 minutes
1,000,000~90 minutes

Tips

  • Cache. Store coordinates in a database — geocoding output for a given address is stable. Never re-geocode the same address twice.
  • Clean addresses first. Add city and country when missing. Vague queries like "Main Street" return the wrong place.
  • Handle empty slots. Batch geocoding returns an empty array for addresses that don't match. Log them for manual review or fall back to city-level geocoding.
  • Add countrycodes. Pass countrycodes=us (or equivalent) if all addresses are in one country — fewer false matches.
  • Reverse batch too. The same batch conveyor works for reverse and lookup: pp.reverse_batch, pp.geocoding.batch.reverse, ReverseBatch, reverse_batch.

Integration guideGeocoding guideServer Integration

Integration guide — SDK packages, architecture patterns, security.