Skip to content

Repository files navigation

// Package.swift dependencies
.package(url: "https://github.com/parseapi/swift", from: "1.8.0")
import ParseAPI

let parse = try ParseAPI("parse_app_...")
let ip = try await parse.ip("8.8.8.8")

Get a key at parseapi.com. In an app, mint an App key on the dashboard and list your bundle identifier on it. The client sends your bundle identifier as X-App-Id automatically. A missing key falls back to the PARSEAPI_KEY environment variable.

API versions

This SDK sends Parse-Version: 2.0.0 on every request, including retries. Its response types match API 2.0.0, and the client selects that contract automatically. No extra constructor setting or key change is needed. This behavior requires the matching API request-version release.

The team setting in Dashboard API version is the default for requests without a version header. This SDK's header takes precedence without changing that saved default. Existing published packages keep their documented behavior.

Test the new SDK dependency in staging, then deploy the same locked dependency with your application code and existing production key. Future major SDK upgrades can deliberately select a newer API contract, so review their migration notes before upgrading. Rolling back the code and dependency restores the contract selected by that SDK release. If the older SDK does not send a version header, its requests use the team default, which must stay unchanged through that rollback window.

Keep the package version locked in your dependency configuration or lockfile. The selected API contract stays fixed across releases within this planned SDK major. See API versions and migration.

Weather from a postal code

Start with the postal code, then pass its coordinates to weather. Reuse the client from the example above.

let place = try await parse.postal("28202", country: "US")
if let lat = place.latitude, let lon = place.longitude {
    let weather = try await parse.weather(lat, lon)
    print(weather)
}

The coordinates represent the postal area. Weather is for that point. Missing coordinates skip the weather lookup. This composition performs two ordinary lookups when coordinates are available, with the retry policy below.

Run the example in your existing async task.

Supply the context you know

Pass country when a postal code or national phone number needs disambiguation. A complete international phone number already carries its country context. For a numeric date such as 03/04/2026, supply the intended format. Defaults resolve what the input establishes. Ambiguous input needs your context.

Results are plain data. Pass a returned code or coordinate to another operation when the task needs it. Check nullable values before composing the next call.

Name paid deep includes flat short, directory, and initials fields beside gender and salutation. nameLocale selects CLDR formatting rules and defaults to en. It changes formatting only. Country remains gender context, and unavailable formatting is null. Older responses may omit these fields.

Display language

This source candidate accepts an optional language for supported display fields. It requires the matching API localization release and data.

let country = try await parse.country("DE", lang: "fr")
print(country.name) // Allemagne

lang applies to this request. The next call uses its usual default unless it also supplies a language. Codes, native names, numeric facts and response structure stay unchanged. Missing translations keep the API's documented fallback. Existing deep rules still apply; Date format and Measure input locale retain their parsing meanings.

Calls

One method per endpoint, named after the route. Async throughout.

try await parse.ip("8.8.8.8")
try await parse.ipSelf()
try await parse.email("hello@gmail.com")
try await parse.vat("DE136695976")
try await parse.bank("DE89370400440532013000")
try await parse.card("424242")
try await parse.provider("1881018208")
try await parse.phone("+14155552671")
try await parse.postal("SW1A 1AA")
try await parse.postal("28202", country: "US")
try await parse.postalNearby("28202", country: "US", radius: 40)
try await parse.postalDistance("28202", "10001", country: "US")
try await parse.city("charlotte", country: "US")
try await parse.cityId("city_mb8mbqrkz8zb")
try await parse.citySearch("char", country: "US", limit: 10)
try await parse.cityNearest(35.2271, -80.8431)
try await parse.cityNearby("denver", radius: 8, unit: "mi")
try await parse.country("US")
try await parse.countryStates("US")
try await parse.state("colorado")
try await parse.state("NC", country: "US")
try await parse.stateDistricts("NC", country: "US")
try await parse.district("37081")
try await parse.continent("NA")
try await parse.continentCountries("NA")
try await parse.bloc("EU")
try await parse.blocCountries("EU")
try await parse.currency("USD")
try await parse.currencyRate("USD", "EUR")
try await parse.language("en")
try await parse.name("BILLY OSHALL")
try await parse.name("Andrea", country: "IT")
try await parse.name("Robert James Smith", deep: true, nameLocale: "en")
try await parse.time() // UTC now
try await parse.time("America/New_York")
try await parse.timeAt(40.7128, -74.006)
try await parse.date("03/04/2026", format: "mdy")
try await parse.dateToday()
try await parse.holiday("US", year: 2026)
try await parse.holidayDate("US", "2026-12-25")
try await parse.elevation(35.2271, -80.8431)
try await parse.point(36.0726, -79.792)
try await parse.weather(40.7128, -74.006)
try await parse.domain("example.com")
try await parse.stack("example.com")
try await parse.asn("AS13335")
try await parse.mac("00:1B:63:84:45:E6")
try await parse.mx("example.com")
try await parse.dns("example.com")
try await parse.dns("_dmarc.example.com", type: "TXT")
try await parse.useragent(uaString)
try await parse.vehicle("1HGCM82633A004352")
try await parse.industry("541511")
try await parse.industrySearch("coffee shop", limit: 5)
try await parse.tariff("8471.30.01.00")
try await parse.tariffSearch("sunglasses")
try await parse.emoji("rocket")
try await parse.emojiSearch("fire")
try await parse.address("123 Main St", country: "US")
try await parse.addressSearch("123 Main", country: "US", postal: "28202")
try await parse.company("01234567", country: "GB")

Industry records include classification exclusions, each with a description and linked codes. Generic exclusions can have no linked codes. Omitted or null exclusions in older responses remain unknown. Search results also include match: the matched field (name, term or naics) and text, plus corrections with from and to tokens for typo fallback. Corrections are empty for exact, plural and prefix matches. Direct code lookups omit match. Older responses may omit it.

Every response is a typed struct. Nullable fields are optionals. Unknown response fields are ignored.

Reuse a client across calls. Each method performs its own lookup and returns data. countryStates("US") fetches the states directly. It does not fetch the country first.

carrier, caller, and hlr are metered lookups for secret keys on a server. App keys answer them with a 403.

DNS uses pooled requests on every plan. Omit type to check A, AAAA, CNAME, MX, NS, TXT, SOA, CAA, SRV and PTR. Records contain name, type, ttl in seconds and a DNS presentation value. TXT values retain quoting and chunk boundaries. A selected question can include its CNAME chain. Empty records mean no records. Lookup failures remain errors.

Time

time returns local ISO at with its UTC offset and integer Unix seconds in unix. With deep: true, deep.offsetSeconds is the exact offset and deep.offsetMinutes is whole minutes. Historical offsets and ISO times can include offset seconds. Omitted at means now. With to or targets, an offsetless at is source wall time. Otherwise it is UTC. Include an offset for repeated local times around a clock change. Current time and conversion use pooled requests on every plan. Coordinate clock fields can be null when the timezone is unknown. Existing timezone methods remain supported.

For an offsetless at with to or targets, choose how to handle a clock change with disambiguation. It applies to named-zone and coordinate Time calls.

Value Repeated time Skipped time
compatible (default) Earlier occurrence Shift forward by the clock change
earlier Earlier occurrence Shift backward by the clock change
later Later occurrence Shift forward by the clock change
reject 400 ambiguous_time 400 nonexistent_time

An explicit UTC offset selects an instant directly. For example, 2026-11-01T01:30:00-04:00 and 2026-11-01T01:30:00-05:00 identify the two New York occurrences. A valid disambiguation value has no effect on explicit instants, current-time requests or lookups without to or targets. For user-entered appointment times, start with reject. Handle ambiguous_time or nonexistent_time by collecting an explicit offset or an earlier/later choice from the user. Other malformed input still uses invalid_request.

let result = try await parse.time("America/New_York", at: "2026-11-01T01:30:00",
    to: "UTC", disambiguation: "later")
if let target = result.to { print(target.at) } // 2026-11-01T06:30:00+00:00

Canonical Time deep includes the pinned rule edition in deep.timezoneDatabaseVersion and source-wall resolution in deep.resolution. Resolution records kind (unique, overlap or gap), the selected policy, signed adjustment_seconds, and chronological alternatives with exact at, Unix seconds and UTC offset. Unique times have an empty alternatives list. Explicit instants, current time and lookups without conversion have null resolution. Destination detail stays compact.

Search serving IANA IDs by city or region, or omit the query to list all (Go and Rust use an empty string). Discovery returns timezone_database_version and sorted timezones. No search matches returns timezones: [].

Pass targets to convert one instant to 1-10 zones in a single pooled request. The native list preserves order and duplicates. Use targets instead of to. The response adds targets, with optional detail inside each target. Unknown source coordinates return targets: null. An unknown destination rejects the whole request with not_found. Omission keeps the original response shape.

let zones = try await parse.timeZones("New York")
let result = try await parse.time("UTC", at: "2026-09-24T12:00:00Z",
    targets: ["America/New_York", "Asia/Tokyo"])
print(zones.timezones)
if let targets = result.targets {
    for target in targets { print(target.timezone, target.at) }
}

Location inputs and timezone filters

try await parse.time(source: .iata("JFK"), deep: true) and try await parse.timeZones(options: TimeZonesOptions(country: "US", dst: false, observesDst: true, details: true)). TimeSource also has ip, city, country, icao, unlocode and address cases.

Choose one explicit location input: IP, exact city name or stable city ID, country, IATA airport, ICAO airport, port UN/LOCODE, or address. Country and state can narrow a city or address. State requires country. Address lookup requires US country context and a strict address-point match. Port lookup covers the reviewed port subset, not every assigned UN/LOCODE. IP lookup always uses the supplied IP.

Location calls add location with status, candidates, truncated, source and the typed input. Check status before using the clock: ambiguous or missing locations retain null time fields. Candidate coordinates and IDs can also be null. A country with multiple timezones does not silently choose one. Named-zone and coordinate calls retain their existing signatures.

Timezone discovery accepts country, IANA area, exact signed offset, abbreviation, DST-at-instant and observes-DST-during-year filters. at selects the common instant, sort selects timezone or offset order, and details adds zones rows plus the evaluation at. The default timezones list stays compact. False DST filters are sent explicitly. An abbreviation returns candidate zones rather than choosing one. Observes-DST uses the UTC calendar year containing at.

Source deep adds standard_offset, standard_offset_seconds, signed dst_offset_seconds and season. Seasonal adjustments can be negative. season describes the current DST-flag interval, or the next within 400 days, with actual before/after transition facts and signed change_seconds. Unknown boundaries remain null. These fields are optional and nullable, and destination deep stays compact.

Measurements

let result = try await parse.measure("5 ft 11 in", to: "cm")
let units = try await parse.measureUnits(unit: "m")

amount is a decimal string, such as "180.34". Without to, the API returns the canonical unit for the measurement type. Pass locale for number formatting and system (us or imperial) when a customary unit needs context. Ambiguous input returns valid: false, a reason, and available choices. Invalid or incompatible target units use the normal API error.

Unit discovery accepts optional query, type, and unit filters. unit selects compatible targets. Omit the filters for the reviewed catalog. Both operations use pooled requests.

Place statistics and optional detail

Australian postal lookups include core localities with suburb choices (city, state, stateName) on every plan. Null or an omitted field means unknown, while [] means the reviewed reference has no eligible choices. city stays null when the source is ambiguous, even if there is only one eligible choice. Let the user select their suburb and keep manual entry available. These are geographic choices, not mailing-address verification. G-NAF source, adaptations and licence.

Postal and District paid profiles include deep.property_tax where supported. It contains annual_median, currency and period: median annual property tax payable on owner-occupied homes in the statistical area. The amount is adjusted to the final year of the reporting period (YYYY-YYYY). This is an area statistic, not a rate or an individual property bill. Unsupported, missing and censored estimates are null.

let place = try await parse.postal("28202", country: "US", deep: true)
let propertyTax = place.deep?.propertyTax

Read population_period alongside population: a reporting year (YYYY) or period (YYYY-YYYY), null when unknown or unverifiable. Keep missing or null values unknown and preserve a known zero. These fields belong to full place profiles. State district lists include each district's population and period. Postal nearby and distance detail remains metropolitan associations only. Continent population stays in core; Continent has no population_period field.

Point returns the timezone ID with the core location. Its optional deep detail adds terrain and compact nearest-city context on every plan. A nearest city is null when none is within 200 km.

Weather returns current conditions by default. Paid deep adds specialist current measurements, forecasts and related detail. A past date is a UTC day and requires deep: it adds deep.history alongside current conditions. Date alone does not request history.

try await parse.weather(40.7128, -74.006, deep: true, date: "2026-08-15")

Tariff starts with the general schedule line. Paid deep adds units and the special and other schedule columns. An optional origin then resolves country-specific measures. The three calls below show those successive choices. Without origin, schedule detail is still returned and origin-dependent fields are null. A null effective rate is not a zero rate.

Tariff lookup and search accept an optional edition fingerprint and date (YYYY-MM-DD). The edition pins exact immutable source bytes. A date is accepted only when verified source coverage exists. An edition without a date returns undated schedule context (date: null). Default requests use today. Paid detail exposes an open-string reason when effective_rate is null, including incomplete_coverage. A null rate never means zero. Explicit selections fail with tariff_selection_mismatch if an older server ignores the requested scope.

Origin means where the goods originate, not where they ship from. The effective rate covers matched stored schedule measures only. It is not complete duty or landed cost.

Codes contain 4, 6, 8 or 10 ASCII digits; dots and whitespace are optional. Search returns up to 20 description matches with parent lineage so a result named "Other" has context. Search is not product classification. In deep, measures: null means origin-dependent measures were not resolved. measures: [] means the resolved lookup found none.

try await parse.tariff("8471.30.01.00")
try await parse.tariff("8471.30.01.00", deep: true)
try await parse.tariff("8471.30.01.00", deep: true, origin: "CN")

Address search uses context from the form: prefer postal, or city and state. An optional end-user ip is a locality hint for server-side calls. An empty result explains itself with reason: more_input, missing_context or no_matches. With suggestions, reason is null. Older responses may omit it, and future reasons remain strings. Catalog and lookup failures use the existing API errors.

HLR reports status at the last check. live means assigned and connected means reachable at that check. Cached results may be returned. Null means unconfirmed. Deep diagnostics stay within the same metered lookup.

Provider lookup

let provider = try await parse.provider("1881018208")
let profile = try await parse.provider("1881018208", deep: true)

Pass the original NPI as a string. valid checks its format and checksum; registered means a match in the stored NPPES snapshot. active reflects recorded NPI deactivation, not licensure. excluded is an NPI-only OIG LEIE match; false is not a complete exclusion clearance. These directory facts do not verify credentials, current practice contact or payment eligibility.

Invalid input returns valid: false with unknown provider fields. A checksum-valid number missing from the snapshot returns registered: false; unavailable storage remains an API error. Preserve null as unknown.

The default pooled lookup includes provider identity, specialty and practice contact where held. Paid deep adds deactivated_at, medicare, opt_out and enrollments from stored source files, with no separate check meter or live verification. enrollments: null means unavailable; [] means no enrollment rows are returned. The API omits unrequested deep and returns {} when requested on Free.

Paid Deep also returns taxonomies in published order, with taxonomy code, specialty label, primary flag and provider-reported license number/state, plus enumerated_at, updated_at and reactivated_at record dates. Reported licenses are not verified licenses. Null lists mean unavailable; empty lists mean the edition contains no entries. Core sources is available on every plan: NPPES, LEIE, PECOS and opt-out each have nullable edition metadata (edition, published_at, through, imported_at). Provider record dates are separate from source publication and completed import dates. Older responses may omit these additions. Edition details remain null until a verified source is served.

Deep

Choose enrichment for the question you need answered.

Operation What deep requests
IP Richer IP fields included with a paid plan. No separate check meter.
Domain Registration dates, registrar, status and DNSSEC, included with a paid plan. Use dns for DNS records and mx for mail routing.
Email A metered mailbox check with deliverability, catch-all, status, reason and address hints, using included email checks or enabled on-demand usage.
VAT A metered registry check where supported, using included VAT checks or enabled on-demand usage.
Phone Numbering-plan state and timezone, pooled on every plan.
Postal, Country, State, City, District Geographic profiles on paid plans. Collections keep deep on each record.
NPI Deactivation date, Medicare enrollment, opt-out and enrollment rows from stored sources on paid plans. Exclusion evidence stays core.
Company, VIN, Industry, Name, Weather Richer reference/profile facts on paid plans.
Time, Date, Currency, Language, Emoji, Bank Optional same-question facts, pooled on every plan.
Point Terrain and compact nearest-city context, pooled on every plan. The timezone ID is core.
Carrier, HLR Place or network details included in the same metered lookup.

Email deep includes mailbox status and the reason for the result, plus a suggested first name, no-reply flag, plus-address tag and mail service. The suggested name is not a verified identity. Unavailable details are null.

Reasons include accepted, invalid_format, invalid_domain, no_mail_server, mailbox_not_found, mailbox_disabled, mailbox_full, catchall, disposable, temporary_failure, rejected and unconfirmed.

Carrier, caller, and HLR are separate metered operations. Choose them explicitly when you need their answers. Ordinary lookups retry twice by default. Metered checks use one attempt by default. Setting retries explicitly can repeat paid usage.

Without deep, the response omits that key. When requested, it is an empty object if access is locked or the operation has no deep fields. Otherwise it contains the available fields. A missing or null field means unknown. Empty collections remain distinct from unknown collections. A list request never becomes a separate billable lookup for each nested record.

let place = try await parse.postal("28202", country: "US")
let profile = try await parse.postal("28202", country: "US", deep: true)
let population = profile.deep?.population
let cities = try await parse.citySearch("char", deep: true)
let cityPopulation = cities.cities.first?.deep?.population

Metered checks require a secret key on a server. App keys can request paid-plan enrichment when their team has access.

let ip = try await parse.ip("52.94.76.10", deep: true)
if ip.deep?.datacenter == true {
    // datacenter IP
}

Bank validation

Bank results include optional checks and issues (BankChecks and BankIssue). Check statuses and issue codes are open strings; handle unknown future values. not_supported means the national check did not run, not that it passed. issues: [] means no applicable check failed; a missing/null value supports older responses. These findings do not establish account existence or ownership. deep.account remains a string so leading zeros are preserved.

Bank lookups send raw input in a JSON body (POST /bank), preserving leading zeros, separators and forbidden characters for server validation. bank keeps its existing call signature and IBAN result. Optional deep.directory identifies the directory edition, country and open-string match grain; absent data remains unknown.

let requirements = try await parse.bankRequirements("US", format: "us_ach")
let result = try await parse.bankUsAch(BankUsAchInput(routing: "021000021", account: "000123456789"))

US ACH checks the routing checksum and supported account format, not account existence, ownership or ACH eligibility. Account checksum status stays not_supported; bank names are nullable partial-directory references. Account text is preserved, including letter case, spaces and hyphens. Requirements describe this validation workflow; they are not every field needed to initiate a payment. Unsupported country/format combinations return supported: false. Omit the format argument for IBAN requirements. The sample is synthetic, not an account to pay.

Errors

Every non-2xx response throws a ParseAPIError with status, code, docs, and requestId, plus nullable retryAfter header metadata. Branch on code.

do {
    _ = try await parse.city("atlantis")
} catch let error as ParseAPIError where error.code == "not_found" {
    // no such city
}

Options

let parse = try ParseAPI(
    "parse_app_...",
    appId: "com.example.weather", // sent as X-App-Id, defaults to your bundle identifier
    timeout: 10,                  // per-attempt timeout in seconds
    retries: nil                  // use the retry default for each endpoint
)

Ordinary lookups retry up to twice on network failures, 429, 500, 502, 503, and 504. Carrier, caller, HLR, and email/VAT deep lookups do not retry automatically. Address deep also uses zero retries, reserved for future verification. An explicit retries value applies to every lookup, including metered requests. A retry may count as another lookup.

Automatic retries honor numeric and HTTP-date Retry-After values up to five seconds. A longer server wait returns the original API error immediately, with the raw header in retryAfter, so the application can schedule a later attempt. Missing or invalid headers use ordinary backoff.

Cancelled tasks stop the lookup and are not retried. Redirects are returned as errors.

Requires Swift 6.0 or later. iOS 15, macOS 12, watchOS 8, tvOS 15. Foundation only, zero dependencies.

Docs

Full field reference for every endpoint: parseapi.com/docs

Compatibility checks

Run swift test and python3 scripts/check-api.py before a release. The API check compares compiler-exported declarations with api/ParseAPI.api. Use python3 scripts/check-api.py --update only after reviewing an intentional API addition.

Pushes and pull requests run the tests on Swift 6.0 and 6.3.3. The API check uses Swift 6.3.3, the compiler used for the baseline. Device-platform validation remains a release check.

Card

Send 2–11 leading digits as a string. Core returns bin, brand, brand_name and a CDN SVG logo. Brand detection uses reviewed network rules independently of issuer records. Unknown or ambiguous prefixes return null brand fields and a generic logo; a known network without reviewed artwork also uses the generic logo.

Optional Deep adds prefix, issuer, country, type and prepaid, included in the same pooled request on every plan. Six or more digits enable directory matching. Fewer digits return all-null Deep fields. Compare deep.prefix with bin: equal is an exact recorded match; shorter is broader; null is no match. The longest row wins, including null fields. prepaid: null means unknown, not false. This is partial reference data, not card validity or payment acceptance.

let card = try await parse.card("51")
print(card.logo)
let details = try await parse.card("43737400", deep: true)
print(details.deep?.prefix as Any)

Leading zeros are preserved. Only ASCII spaces, tabs, CR, LF and hyphens are removed; raw input is limited to 64 characters. Invalid prefixes are rejected before dispatch, accepted input is forwarded unchanged. Never send a full card number.

Stack

let result = try await parse.stack("example.com")

Pass a public hostname without a scheme, path, port or IP address. Stack returns the homepage URL and checked_at time, then eight technology arrays: cms, servers, frameworks, ecommerce, analytics, chat, payments and hosting. Each entry contains a technology code, name and nullable version. Multiple CMSs or servers remain separate entries. Empty arrays mean no matches in the checked pages. An unsuccessful check returns null arrays and a null checked_at.

scope identifies homepage or site coverage. pages counts successfully checked HTML pages. partial is true for a homepage-only or incomplete bounded site check, false when the known in-scope candidates finished, and null when no check succeeded. False does not guarantee that every page on the website was discovered.

The complete technology result is included in the core response. The generic deep=true option adds only an empty object and is unnecessary for Stack. Successful checks may be reused for up to 24 hours. pretty optionally formats the wire JSON. Each lookup uses one request and API version 2.0.0 selected by this client.

Stack defaults to 35 seconds per attempt so a first scan has time to finish. Other lookups retain their 10-second default. An explicit client timeout takes precedence.

Vehicle lookups use vin as the input and response field. Existing VIN methods remain available for compatibility.

About

Official parseAPI client

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages