API Reference

Authentication

The Addresspenny API uses Bearer token authentication. Include your API token in the Authorization header with every request.

Authorization: Bearer your_api_token

You can create and manage API tokens from the API Tokens page. See the Authentication guide for details.

Base URL

https://addresspenny.com/api/v1

All endpoints are scoped to an account: /api/v1/accounts/:account_id/...

Addresses

Validate an address

Submits an address for validation. The address is created with a pending status while validation runs asynchronously in the background. Fetch the address again to check when the result is ready.

POST /api/v1/accounts/:account_id/addresses

Request body:

{
  "address": {
    "original_input": "30 rockefeller plaza new york ny"
  }
}

The original_input can be a full or partial address as a single string. The validation service will parse and standardize it.

Response: 201 Created

{
  "address": {
    "id": 1,
    "original_input": "30 rockefeller plaza new york ny",
    "status": "pending",
    "created_at": "2026-03-31T12:00:00Z",
    "updated_at": "2026-03-31T12:00:00Z"
  }
}

Get an address

Returns a single address. Once validation completes, the remote_payload contains the full result.

GET /api/v1/accounts/:account_id/addresses/:id

Response: 200 OK

{
  "address": {
    "id": 1,
    "original_input": "30 rockefeller plaza new york ny",
    "status": "validated",
    "remote_payload": {
      "address": {
        "city": "New York",
        "line1": "30 Rockefeller Plaza",
        "line2": null,
        "state": "NY",
        "country": "US",
        "postal_code": "10112-0015"
      },
      "is_valid": false,
      "original_address": {
        "city": "",
        "lines": ["30 rockefeller plaza new york ny"],
        "state": "",
        "country": "US",
        "postal_code": ""
      },
      "formatted_address": "30 Rockefeller Plaza, New York, NY 10112-0015, USA",
      "validation_results": {
        "messages": [
          {
            "code": "street_number.confirmed",
            "text": "Street number confirmed",
            "type": "info",
            "source": "google_maps"
          },
          {
            "code": "route.confirmed",
            "text": "Route confirmed",
            "type": "info",
            "source": "google_maps"
          },
          {
            "code": "locality.confirmed",
            "text": "Locality confirmed",
            "type": "info",
            "source": "google_maps"
          },
          {
            "code": "postal_code.confirmed",
            "text": "Postal code confirmed",
            "type": "info",
            "source": "google_maps"
          }
        ],
        "granularity": "premise"
      }
    },
    "created_at": "2026-03-31T12:00:00Z",
    "updated_at": "2026-03-31T12:00:05Z"
  }
}

List addresses

Returns all addresses for the account, most recent first.

GET /api/v1/accounts/:account_id/addresses

Response: 200 OK

{
  "addresses": [
    {
      "id": 2,
      "original_input": "350 Fifth Avenue, New York, NY",
      "status": "pending",
      "created_at": "2026-03-31T12:01:00Z",
      "updated_at": "2026-03-31T12:01:00Z"
    },
    {
      "id": 1,
      "original_input": "30 rockefeller plaza new york ny",
      "status": "validated",
      "remote_payload": { "..." : "..." },
      "created_at": "2026-03-31T12:00:00Z",
      "updated_at": "2026-03-31T12:00:05Z"
    }
  ]
}

Batch validate addresses

Submit multiple addresses for validation in a single request. Each address consumes one credit. Maximum batch size is 100.

POST /api/v1/accounts/:account_id/addresses/batch

Required scope: addresses:write

Request body:

{
  "addresses": [
    "1600 Amphitheatre Pkwy, Mountain View, CA",
    "350 Fifth Avenue, New York, NY",
    "1060 W Addison St, Chicago, IL"
  ]
}

Response: 201 Created

{
  "addresses": [
    {
      "id": 10,
      "original_input": "1600 Amphitheatre Pkwy, Mountain View, CA",
      "status": "pending",
      "created_at": "2026-04-04T12:00:00Z",
      "updated_at": "2026-04-04T12:00:00Z"
    },
    {
      "id": 11,
      "original_input": "350 Fifth Avenue, New York, NY",
      "status": "pending",
      "created_at": "2026-04-04T12:00:00Z",
      "updated_at": "2026-04-04T12:00:00Z"
    },
    {
      "id": 12,
      "original_input": "1060 W Addison St, Chicago, IL",
      "status": "pending",
      "created_at": "2026-04-04T12:00:00Z",
      "updated_at": "2026-04-04T12:00:00Z"
    }
  ]
}

Individual addresses that fail validation (e.g., blank input) will include an errors array instead of the address fields. Successfully created addresses in the same batch are not affected.

Scopes

API tokens can be created with specific permission scopes. Tokens with no scopes have full access (legacy behavior). Available scopes:

  • addresses:read — list and get addresses
  • addresses:write — create and batch validate addresses
  • webhooks:read — list webhook subscriptions
  • webhooks:manage — create and delete webhook subscriptions
  • account:read — read account information

Requests with insufficient scope receive 403 Forbidden with details about the required and granted scopes.

Rate limits

API requests are rate limited per token:

  • Read endpoints (index, show): 120 requests per minute
  • Write endpoints (create, batch): 60 requests per minute

When rate limited, the API returns 429 Too Many Requests:

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please retry after 60 seconds.",
    "retry_after": 60
  }
}

Idempotency

For safe retries, include an Idempotency-Key header with any POST request. If the same key is sent again with the same request body, the API returns the original cached response without re-processing.

curl -X POST https://addresspenny.com/api/v1/accounts/acct_1a2b3c/addresses \
  -H "Authorization: Bearer your_api_token" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-123" \
  -d '{"address": {"original_input": "1600 Amphitheatre Pkwy, Mountain View, CA"}}'
  • Keys expire after 24 hours
  • Reusing a key with a different request body returns 409 Conflict
  • The header is optional — omit it for normal (non-idempotent) behavior
  • Only applies to POST endpoints (GET requests are naturally idempotent)

Response fields

Address object

  • id — unique address identifier
  • original_input — the raw string you submitted
  • statuspending, validated, or failed
  • remote_payload — validation results (only present when validated)
  • created_at / updated_at — timestamps

Validation payload

The remote_payload contains the parsed and validated address data:

  • is_valid — whether the address is fully deliverable. An address can have all components confirmed but still be false if something is missing (e.g., an apartment or suite number).
  • formatted_address — the standardized, single-line address
  • address — the parsed address components (line1, line2, city, state, postal_code, country)
  • original_address — how the validation service interpreted your raw input before processing
  • validation_results.messages — component-level confirmation or warning messages from Google Maps, each with a code, text, and type (info, warning, or error)
  • validation_results.granularity — how precisely the address was matched: premise (exact building), sub_premise (unit level), route (street level only), etc.

Address status

  • pending — validation is in progress (typically completes in a few seconds)
  • validated — validation complete, remote_payload contains results
  • failed — validation could not be completed (e.g., service unavailable)

Errors

The API returns structured error objects with a code and message.

Insufficient credits (402)

Returned when the account has no remaining address credits. Each validation consumes one credit.

{
  "error": {
    "code": "insufficient_credits",
    "message": "No address credits remaining",
    "credits_remaining": 0,
    "credits_limit": 50
  }
}

Validation failed (422)

Returned when the request body is invalid. Includes a details array with per-field errors.

{
  "error": {
    "code": "validation_failed",
    "message": "Original input can't be blank",
    "details": [
      { "field": "original_input", "message": "can't be blank" }
    ]
  }
}

Insufficient scope (403)

Returned when the API token does not have the required permission scope.

{
  "error": {
    "code": "insufficient_scope",
    "message": "Token does not have the required scope: addresses:write",
    "required_scope": "addresses:write",
    "granted_scopes": ["addresses:read", "account:read"]
  }
}

Rate limited (429)

Returned when the token has exceeded the request rate limit.

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please retry after 60 seconds.",
    "retry_after": 60
  }
}

Idempotency conflict (409)

Returned when an Idempotency-Key is reused with a different request body.

{
  "error": {
    "code": "idempotency_conflict",
    "message": "Idempotency key already used with different request body"
  }
}

Unauthorized (401)

Returned when the API token is missing or invalid.