{"components":{"parameters":{"CID":{"description":"Google CID. Resolved server-side to the business observed under (provider=\"google\", providerBusinessId=cid) — the only provider phase two supports.","in":"path","name":"cid","required":true,"schema":{"type":"string"}},"JobID":{"description":"Search identifier.","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},"ResourceID":{"description":"Resource identifier.","in":"path","name":"id","required":true,"schema":{"type":"string"}}},"responses":{"BadRequest":{"content":{"application/json":{"example":{"error":"invalid_body"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The JSON body, query parameter, or field value is invalid."},"Conflict":{"content":{"application/json":{"example":{"error":"search_not_deletable"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The operation conflicts with the resource's current state."},"Forbidden":{"content":{"application/json":{"example":{"error":"insufficient_scope"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The API key is valid but lacks the required scope."},"InternalError":{"content":{"application/json":{"example":{"error":"internal_error"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The request could not be completed because of an internal error."},"NotFound":{"content":{"application/json":{"example":{"error":"not_found"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The requested resource does not exist for the authenticated user."},"PaymentRequired":{"content":{"application/json":{"example":{"error":"insufficient_credits"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"An active subscription or sufficient credits are required."},"RateLimited":{"content":{"application/json":{"example":{"error":"rate_limit_exceeded"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The API key exceeded its effective per-minute request limit."},"Unauthorized":{"content":{"application/json":{"example":{"error":"unauthorized"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The request has no valid API key."},"Unavailable":{"content":{"application/json":{"example":{"error":"usage_unavailable"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"A required downstream service is temporarily unavailable."}},"schemas":{"APIKey":{"properties":{"createdAt":{"format":"date-time","type":"string"},"enabled":{"type":"boolean"},"expiresAt":{"format":"date-time","type":["string","null"]},"id":{"type":"string"},"lastRequest":{"format":"date-time","type":["string","null"]},"name":{"type":["string","null"]},"prefix":{"type":["string","null"]},"scopes":{"items":{"type":"string"},"type":["array","null"]},"start":{"type":["string","null"]}},"required":["id","enabled","scopes","createdAt"],"type":"object"},"Business":{"description":"A business's identity: a system-wide fact observed across every account's runs, not customer data. No account owns this record.","properties":{"cid":{"description":"Mirrors providerBusinessId for provider=\"google\"; null for any other provider.","type":["string","null"]},"createdAt":{"format":"date-time","type":"string"},"domain":{"type":["string","null"]},"id":{"format":"uuid","type":"string"},"latestRun":{"$ref":"#/components/schemas/BusinessRunSummary","description":"Your account's most recent research or monitor run against this business. Absent if you have never started one — this is the one owner-scoped fact this endpoint adds to the otherwise shared record."},"name":{"description":"Most recently observed name. Not authoritative — a list-view convenience, not a verified legal name.","type":["string","null"]},"placeId":{"type":["string","null"]},"provider":{"description":"Phase two only ever populates \"google\".","enum":["google","bing","apple"],"type":"string"},"providerBusinessId":{"description":"The provider's own identifier — the Google CID for provider=\"google\".","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","provider","providerBusinessId","createdAt","updatedAt"],"type":"object"},"BusinessDatasetRow":{"properties":{"categories":{"items":{"type":"string"},"type":"array"},"cid":{"description":"Google CID — a platform-native alias, equal to providerBusinessId on Google rows and null elsewhere. Returned free so you can match this row against data you already hold.","type":["string","null"]},"country":{"type":["string","null"]},"credits":{"description":"Credits this row costs to unlock. Zero once owned.","type":"integer"},"emailCount":{"description":"How many emails this business has. A count, not the addresses.","type":"integer"},"emailStatuses":{"description":"Distinct states of this business's emails, sorted. Lets you tell a usable address from a dead one before paying.","items":{"$ref":"#/components/schemas/EmailStatus"},"type":"array"},"hasEmail":{"type":"boolean"},"hasPhone":{"description":"Presence only. The value is what unlocking buys.","type":"boolean"},"hasWebsite":{"type":"boolean"},"locality":{"type":["string","null"]},"name":{"type":"string"},"observedAt":{"format":"date-time","type":"string"},"owned":{"description":"Already in this account's leads, from a past run or a past unlock. Unlocking it again costs nothing.","type":"boolean"},"placeId":{"description":"Google Place ID. Returned free, same reason as cid.","type":["string","null"]},"provider":{"description":"Which map this row came from.","enum":["google","apple","bing"],"type":"string"},"providerBusinessId":{"description":"That map's own identifier for this business. Together with provider it identifies the row; pass both to POST /v1/businesses/unlock.","type":"string"},"rating":{"type":["number","null"]},"reviewCount":{"type":["integer","null"]}},"required":["provider","providerBusinessId","name","categories","hasPhone","hasWebsite","hasEmail","emailCount","emailStatuses","owned","credits","observedAt"],"type":"object"},"BusinessMonitorSubscription":{"description":"Your standing subscription to a business's change checks. Keyed on your account and the business, never on the lead that started it.","properties":{"businessId":{"format":"uuid","type":"string"},"createdAt":{"format":"date-time","type":"string"},"enabled":{"type":"boolean"},"id":{"format":"uuid","type":"string"},"intervalDays":{"minimum":1,"type":"integer"},"nextCheckAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","businessId","intervalDays","enabled","nextCheckAt","createdAt","updatedAt"],"type":"object"},"BusinessRef":{"description":"Identifies one row: which map it came from, plus that map's own identifier for it. The two fields together are the identity — the three maps do not share an identifier namespace, so provider is not redundant.","properties":{"id":{"description":"The map's own identifier: Google CID (727846742557342929), Apple Place ID (I63802885C8189B2B), or Bing ypid (YN873x14475615034754698960). Pass identifiers you already hold from any source — they resolve here directly.","type":"string"},"provider":{"enum":["google","apple","bing"],"type":"string"}},"required":["provider","id"],"type":"object"},"BusinessReviewsResponse":{"properties":{"analysis":{"description":"Your interpretation of this business's reviews. Null until a research run has produced one for your account — the same reviews mean something different to a different account, so there is never a shared default to fall back to.","properties":{"analyzedAt":{"format":"date-time","type":"string"},"narrative":{"description":"Structured interpretation output. Shape owned by the analysis worker, not fixed here."}},"required":["narrative","analyzedAt"],"type":["object","null"]},"businessId":{"format":"uuid","type":"string"},"signal":{"description":"Present only alongside a non-null analysis: the structured, intent-neutral extraction (topics, sentiment) that analysis was built from. Shared across every account that has researched this business — never opinionated about what it means for you.","properties":{"extractedAt":{"format":"date-time","type":"string"},"extractionVersion":{"description":"Model/prompt identifier, not a timestamp — extraction can be redone against the same snapshot under a new version.","type":"string"},"sentimentSummary":{"description":"Overall sentiment distribution across the reviews."},"topics":{"description":"Structured topic/sentiment breakdown. Shape owned by the extraction worker, not fixed here."}},"type":"object"}},"required":["businessId","analysis"],"type":"object"},"BusinessRun":{"description":"A one-shot research run or a monitor run's check, both created the same way and billed at creation time.","properties":{"businessId":{"format":"uuid","type":"string"},"createdAt":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"kind":{"enum":["research","monitor"],"type":"string"},"status":{"enum":["pending","running","completed","failed","canceled"],"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","kind","status","createdAt","updatedAt"],"type":"object"},"BusinessRunSummary":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"kind":{"enum":["research","monitor"],"type":"string"},"status":{"enum":["pending","running","completed","failed","canceled"],"type":"string"}},"required":["id","kind","status","createdAt"],"type":"object"},"CreateAPIKeyRequest":{"additionalProperties":false,"properties":{"expiresInDays":{"maximum":3650,"minimum":1,"type":["integer","null"]},"name":{"maxLength":64,"minLength":1,"type":["string","null"]},"scopes":{"items":{"enum":["business:read","business:unlock","billing:write","searches:read","searches:write","keys:read","keys:write","leads:export","leads:read","leads:write","usage:read","webhooks:read","webhooks:write"],"type":"string"},"minItems":1,"type":["array","null"],"uniqueItems":true}},"type":"object"},"CreateSearchRequest":{"additionalProperties":false,"properties":{"conversationId":{"description":"Links this search to an assistant conversation you own. Optional, and only meaningful to the MapLeads dashboard — API integrations can ignore it. A conversation id belonging to another account is rejected.","format":"uuid","type":"string"},"country":{"description":"Optional two-letter country code.","pattern":"^[A-Za-z]{2}$","type":["string","null"]},"keyword":{"description":"Business category or search query.","maxLength":200,"minLength":1,"type":"string"},"lang":{"default":"en","description":"Language code used for the maps query.","maxLength":10,"minLength":2,"type":"string"},"location":{"description":"Optional city, region, address, or postal-code hint.","maxLength":200,"type":["string","null"]},"options":{"$ref":"#/components/schemas/SearchOptions"},"provider":{"default":"google","description":"Map source to search.","enum":["google","bing","apple"],"type":"string"}},"required":["keyword"],"type":"object"},"CreateWebhookRequest":{"additionalProperties":false,"properties":{"eventTypes":{"items":{"enum":["search.completed","search.failed"],"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"url":{"format":"uri","maxLength":2048,"type":"string"}},"required":["url","eventTypes"],"type":"object"},"CreatedWebhook":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"properties":{"secret":{"example":"whsec_xxx","type":"string"}},"required":["secret"],"type":"object"}]},"EmailStatus":{"description":"State of an email address, as verified before the row was listed. `deliverable` is a real person's address that will accept mail; `role` is a shared inbox such as info@ — for many small businesses the only door there is, so it is reported rather than dropped; `catch_all` accepts anything and so verifies neither way; `invalid` will not deliver and is still reported, which is how you know it was checked; `verification_failed` means we could not check it, as distinct from `unverified` which means we have not yet; `none` is a business with no email at all.","enum":["deliverable","role","catch_all","risky","invalid","unverified","verification_failed","unknown","none"],"type":"string"},"Error":{"additionalProperties":false,"properties":{"error":{"description":"Stable machine-readable error code.","type":"string"}},"required":["error"],"type":"object"},"JobProgress":{"properties":{"failed":{"minimum":0,"type":"integer"},"found":{"minimum":0,"type":"integer"},"phase":{"enum":["searching","enriching","wrapping_up","done","failed"],"type":"string"},"processed":{"minimum":0,"type":"integer"}},"required":["phase","found","processed","failed"],"type":"object"},"Lead":{"additionalProperties":true,"description":"A public business record returned by a map provider. Enrichment fields are present only when available and entitled.","properties":{"categories":{"items":{"type":"string"},"type":"array"},"emails":{"items":{"additionalProperties":true,"type":"object"},"type":"array"},"fullAddress":{"type":["string","null"]},"name":{"type":"string"},"phone":{"type":["string","null"]},"rating":{"type":["number","null"]},"reviewCount":{"type":["integer","null"]},"socials":{"additionalProperties":{"format":"uri","type":"string"},"type":"object"},"website":{"format":"uri","type":["string","null"]}},"required":["name"],"type":"object"},"Search":{"properties":{"createdAt":{"format":"date-time","type":"string"},"error":{"type":["string","null"]},"id":{"format":"uuid","type":"string"},"keyword":{"type":"string"},"language":{"type":"string"},"location":{"type":["string","null"]},"maxResults":{"minimum":0,"type":"integer"},"progress":{"$ref":"#/components/schemas/JobProgress"},"provider":{"enum":["google","bing","apple"],"type":"string"},"resultCount":{"minimum":0,"type":"integer"},"status":{"enum":["pending","running","completed","failed","canceled"],"type":"string"}},"required":["id","provider","status","keyword","language","maxResults","resultCount","createdAt","progress"],"type":"object"},"SearchOptions":{"additionalProperties":false,"properties":{"enrichEmail":{"description":"Discover and verify public business emails from merchant websites when the plan supports enrichment.","type":"boolean"},"enrichSocials":{"description":"Discover public social profiles from merchant websites when the plan supports enrichment.","type":"boolean"},"maxResults":{"default":200,"description":"Maximum leads to extract. Zero uses the default.","maximum":2000,"minimum":0,"type":"integer"}},"type":"object"},"Success":{"properties":{"ok":{"const":true,"type":"boolean"}},"required":["ok"],"type":"object"},"UnlockBusinessesRequest":{"description":"Rows to buy, identified the same way the listing hands them out.","properties":{"businesses":{"description":"Rows already owned come back in alreadyOwned and are not charged. Identifiers that resolve to the same row are charged once.","items":{"$ref":"#/components/schemas/BusinessRef"},"maxItems":500,"type":"array"}},"required":["businesses"],"type":"object"},"Usage":{"properties":{"accessStatus":{"type":"string"},"credits":{"properties":{"balance":{"minimum":0,"type":"integer"},"monthlyAllowance":{"minimum":0,"type":"integer"}},"required":["balance","monthlyAllowance"],"type":"object"},"plan":{"enum":["lite","pro","max",null],"type":["string","null"]},"rateLimitPerMinute":{"minimum":1,"type":["integer","null"]},"resetsAt":{"format":"date-time","type":["string","null"]}},"required":["plan","accessStatus","credits","rateLimitPerMinute","resetsAt"],"type":"object"},"Webhook":{"properties":{"createdAt":{"format":"date-time","type":"string"},"enabled":{"type":"boolean"},"eventTypes":{"items":{"type":"string"},"type":"array"},"id":{"type":"string"},"url":{"format":"uri","type":"string"}},"required":["id","url","eventTypes","enabled","createdAt"],"type":"object"}},"securitySchemes":{"apiKeyHeader":{"description":"Pass the same `mapleads_xxx` credential in the `x-api-key` header.","in":"header","name":"x-api-key","type":"apiKey"},"bearer":{"bearerFormat":"MapLeads API key","description":"Pass `Authorization: Bearer mapleads_xxx`.","scheme":"bearer","type":"http"}}},"info":{"description":"Public developer API for creating asynchronous Google, Bing, and Apple Maps lead extraction jobs, then retrieving or exporting the resulting business leads. Site-runtime market content endpoints are intentionally outside this customer API contract.","title":"MapLeads API","version":"1.0.0"},"openapi":"3.1.0","paths":{"/v1/businesses":{"get":{"description":"Server-side filtered, keyset-paginated view of the shared business dataset. Contact values (phone, email, website) are never returned for rows you do not own — only `hasPhone`/`hasEmail`/`hasWebsite` presence flags. Unlock a row to get the values. Pass `cursor` from the previous response to page; never build one yourself. Requires the `business:read` scope when called with a scoped key.","operationId":"listBusinesses","parameters":[{"description":"Source maps to keep. Repeat the parameter or comma-separate. Omit to include all three — there is no default map. The same business can appear once per map; those are separate rows with separate identifiers, not duplicates.","explode":false,"in":"query","name":"provider","required":false,"schema":{"items":{"enum":["google","apple","bing"],"type":"string"},"type":"array"},"style":"form"},{"description":"Free text over name and address.","in":"query","name":"q","required":false,"schema":{"type":"string"}},{"description":"Exact country match.","in":"query","name":"country","required":false,"schema":{"type":"string"}},{"description":"Exact city/locality match.","in":"query","name":"locality","required":false,"schema":{"type":"string"}},{"description":"Row has this category.","in":"query","name":"category","required":false,"schema":{"type":"string"}},{"description":"Only rows rated at least this.","in":"query","name":"minRating","required":false,"schema":{"type":"number"}},{"description":"Three-state. `true` keeps only rows that have a phone number, `false` keeps only rows that have none, and omitting the parameter does not filter on it. Any other value is a 400 — a typo must not silently widen the result set.","in":"query","name":"hasPhone","required":false,"schema":{"type":"boolean"}},{"description":"Three-state. `true` keeps only rows that have a website, `false` keeps only rows that have none, and omitting the parameter does not filter on it. Any other value is a 400 — a typo must not silently widen the result set.","in":"query","name":"hasWebsite","required":false,"schema":{"type":"boolean"}},{"description":"Three-state. `true` keeps only rows that have an email address, `false` keeps only rows that have none, and omitting the parameter does not filter on it. Any other value is a 400 — a typo must not silently widen the result set.","in":"query","name":"hasEmail","required":false,"schema":{"type":"boolean"}},{"description":"Rows per page (default 50, max 200).","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Opaque keyset cursor from the previous response. The way to walk the whole dataset: cost per page stays flat however deep you go. Mutually exclusive with `page`.","in":"query","name":"cursor","required":false,"schema":{"type":"string"}},{"description":"1-based page number, for a numbered pager. Costs an offset, so it is capped at 500; past that the response is a 400 naming `cursor` as the way to go further. Mutually exclusive with `cursor`. Only when this is set does the response carry `page`, because counting matches is not free and is not done unasked.","in":"query","name":"page","required":false,"schema":{"maximum":500,"minimum":1,"type":"integer"}},{"description":"Three-state, like the presence filters. `true` keeps only rows this account already has, `false` keeps only rows it does not, and omitting the parameter does not filter on it. The web app sends `false` by default — unlocking is the only thing on offer here — but it is a default, not a rule: a row stays reachable straight after it is unlocked.","in":"query","name":"owned","required":false,"schema":{"type":"boolean"}},{"description":"Keep rows with at least one email in any of these states. Any-match, not all-match: a business with one info@ and one real person belongs in a deliverable search. An unrecognised value is a 400 rather than a silently ignored filter, because ignoring it returns far more rows than were asked for.","explode":false,"in":"query","name":"emailStatus","schema":{"items":{"$ref":"#/components/schemas/EmailStatus"},"type":"array"},"style":"form"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/BusinessDatasetRow"},"type":"array"},"nextCursor":{"description":"Absent on the last page. Only when paging by `cursor`.","type":"string"},"page":{"description":"Only when paging by `page`.","properties":{"maxPage":{"description":"The deepest page that can be requested. May be less than ceil(total / size) — use `cursor` past this point.","type":"integer"},"number":{"description":"1-based, echoing what was asked for.","type":"integer"},"size":{"type":"integer"},"total":{"description":"How many rows match. Check `totalIsExact` before doing arithmetic with it: counting an eight-figure table exactly is not free, so past a threshold this is the query planner’s estimate.","type":"integer"},"totalIsExact":{"description":"False when `total` is an estimate rather than a real count.","type":"boolean"}},"required":["number","size","total","totalIsExact","maxPage"],"type":"object"}},"required":["data"],"type":"object"}}},"description":"A page of dataset rows"},"400":{"description":"Invalid cursor, invalid filter value, page past the cap, or `page` and `cursor` sent together"},"401":{"description":"Missing or invalid credentials"}},"summary":"Browse the business dataset","tags":["Businesses"]}},"/v1/businesses/unlock":{"post":{"description":"Buys rows into this account's leads, charging the same credits a search result costs. Idempotent per row: anything already owned comes back in `alreadyOwned` and is not charged, so a double-submit or a retry cannot bill twice. Insufficient balance rejects the whole batch with 402 rather than unlocking part of it — a partial unlock leaves you unable to tell which rows you bought. Requires the `business:unlock` scope when called with a scoped key.","operationId":"unlockBusinesses","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnlockBusinessesRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"alreadyOwned":{"description":"Rows you already own, echoed back in the same shape you sent. Not charged.","items":{"$ref":"#/components/schemas/BusinessRef"},"type":"array"},"creditsSpent":{"type":"integer"},"unlocked":{"items":{"properties":{"cid":{"type":"string"},"id":{"format":"uuid","type":"string"}},"required":["id","cid"],"type":"object"},"type":"array"}},"required":["unlocked","creditsSpent","alreadyOwned"],"type":"object"}}},"description":"What was unlocked and what it cost"},"400":{"description":"Missing or oversized cids list"},"401":{"description":"Missing or invalid credentials"},"402":{"description":"Not enough credits for the whole batch"}},"summary":"Unlock dataset rows into your leads","tags":["Businesses"]}},"/v1/businesses/{cid}":{"get":{"description":"Resolves a Google CID to the shared business record: identity fields observed across every account's runs, plus — when you have started one — the status of your own most recent research or monitor run against it. Returns 404 until some run has actually observed this CID. Requires the `business:read` scope when called with a scoped key.","operationId":"getBusiness","parameters":[{"$ref":"#/components/parameters/CID"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Business"}}},"description":"Business"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Get a business","tags":["Businesses"]}},"/v1/keys":{"get":{"description":"Lists credential metadata. Secret key values are never returned after creation. Requires the `keys:read` scope when called with a scoped key.","operationId":"listAPIKeys","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/APIKey"},"type":"array"}},"required":["keys"],"type":"object"}}},"description":"API keys"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"List API keys","tags":["API keys"]},"post":{"description":"Creates a scoped credential. The full `mapleads_` key is returned once and must be stored securely. Requires the `keys:write` scope when called with a scoped key.","operationId":"createAPIKey","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAPIKeyRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"apiKey":{"$ref":"#/components/schemas/APIKey"},"key":{"example":"mapleads_xxx","type":"string"}},"required":["key","apiKey"],"type":"object"}}},"description":"API key created"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Create an API key","tags":["API keys"]}},"/v1/keys/{id}":{"delete":{"description":"Permanently revokes a credential. Requires the `keys:write` scope when called with a scoped key.","operationId":"deleteAPIKey","parameters":[{"$ref":"#/components/parameters/ResourceID"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}},"description":"API key revoked"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Revoke an API key","tags":["API keys"]},"patch":{"description":"Updates the credential's display name. Requires the `keys:write` scope when called with a scoped key.","operationId":"updateAPIKey","parameters":[{"$ref":"#/components/parameters/ResourceID"}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"name":{"maxLength":64,"minLength":1,"type":"string"}},"required":["name"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"apiKey":{"$ref":"#/components/schemas/APIKey"}},"required":["apiKey"],"type":"object"}}},"description":"API key updated"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Rename an API key","tags":["API keys"]}},"/v1/leads":{"get":{"description":"Every lead you own, regardless of how it got here — searches and dataset unlocks alike. Scoping leads to a search only ever worked because search was the single way in; an unlocked row has no job and would be invisible. Keyset-paginated newest first; pass `cursor` from the previous response. Requires the `leads:read` scope when called with a scoped key.","operationId":"listLeads","parameters":[{"description":"Source maps to keep. Repeat the parameter or comma-separate. Omit to include all three — there is no default map. The same business can appear once per map; those are separate rows with separate identifiers, not duplicates.","explode":false,"in":"query","name":"provider","required":false,"schema":{"items":{"enum":["google","apple","bing"],"type":"string"},"type":"array"},"style":"form"},{"description":"Rows per page (default 100, max 500).","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Opaque keyset cursor from the previous response.","in":"query","name":"cursor","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"leads":{"items":{"type":"object"},"type":"array"},"nextCursor":{"description":"Absent on the last page.","type":"string"}},"required":["leads"],"type":"object"}}},"description":"A page of leads"},"400":{"description":"Invalid cursor or limit"},"401":{"description":"Missing or invalid credentials"}},"summary":"List every lead in this account","tags":["Leads"]}},"/v1/leads/export":{"get":{"description":"Downloads everything this account owns as one file — searches and dataset unlocks alike. Unlocked rows have no search behind them, so this is the only way to get them out in bulk. Free: you paid for these rows once already. Requires the `leads:export` scope when called with a scoped key.","operationId":"exportLeads","parameters":[{"description":"Source maps to keep. Omit to include all three.","explode":false,"in":"query","name":"provider","required":false,"schema":{"items":{"enum":["google","apple","bing"],"type":"string"},"type":"array"},"style":"form"},{"description":"Output format.","in":"query","name":"format","required":false,"schema":{"default":"csv","enum":["csv","xlsx","json"],"type":"string"}},{"description":"Optional. Repeat to export only these leads.","in":"query","name":"leadIds","required":false,"schema":{"items":{"type":"string"},"type":"array"}}],"responses":{"200":{"content":{"text/csv":{"schema":{"format":"binary","type":"string"}}},"description":"The file, with a Content-Disposition attachment header"},"400":{"description":"Invalid cursor or limit"},"401":{"description":"Missing or invalid credentials"}},"summary":"Export all your leads","tags":["Leads"]}},"/v1/leads/{id}":{"get":{"description":"Returns a single lead this account owns, in the same shape the list returns. A lead belonging to someone else is 404, not 403 — a 403 would confirm the id is real. Free. Requires the `leads:read` scope when called with a scoped key.","operationId":"getLead","parameters":[{"description":"Lead id.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lead"}}},"description":"The lead"},"400":{"description":"Invalid cursor or limit"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"No such lead in this account"}},"summary":"Get one of your leads","tags":["Leads"]}},"/v1/searches":{"get":{"description":"Returns up to the 50 most recent searches owned by the authenticated user. Requires the `searches:read` scope when called with a scoped key.","operationId":"listSearches","parameters":[{"description":"Source maps to keep. Repeat the parameter or comma-separate. Omit to include all three — there is no default map. The same business can appear once per map; those are separate rows with separate identifiers, not duplicates.","explode":false,"in":"query","name":"provider","required":false,"schema":{"items":{"enum":["google","apple","bing"],"type":"string"},"type":"array"},"style":"form"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Search"},"type":"array"}},"required":["data"],"type":"object"}}},"description":"Searches"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"List searches","tags":["Searches"]},"post":{"description":"Starts one asynchronous maps search. The job begins in `pending`; poll the returned job URL until it reaches `completed` or `failed`. Creating a job requires an active subscription or trial, and available credits. Requires the `searches:write` scope when called with a scoped key.","operationId":"createSearch","requestBody":{"content":{"application/json":{"example":{"country":"US","keyword":"design agencies","lang":"en","location":"New York, NY","options":{"enrichEmail":true,"enrichSocials":true,"maxResults":100},"provider":"google"},"schema":{"$ref":"#/components/schemas/CreateSearchRequest"}}},"description":"Maps query and extraction options.","required":true},"responses":{"201":{"content":{"application/json":{"example":{"createdAt":"2026-08-01T10:00:00Z","error":null,"id":"018f275d-5475-7dc8-bd84-4f319efab001","keyword":"design agencies","language":"en","location":"New York, NY","maxResults":100,"progress":{"failed":0,"found":0,"phase":"searching","processed":0},"provider":"google","resultCount":0,"status":"pending"},"schema":{"$ref":"#/components/schemas/Search"}}},"description":"Search created"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Create a search","tags":["Searches"]}},"/v1/searches/{id}":{"delete":{"description":"Deletes a job when its current state is eligible for deletion. Jobs that are still running return a conflict. Requires the `searches:write` scope when called with a scoped key.","operationId":"deleteSearch","parameters":[{"$ref":"#/components/parameters/JobID"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"deleted":{"const":true,"type":"boolean"}},"required":["deleted"],"type":"object"}}},"description":"Job deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Delete a terminal search","tags":["Searches"]},"get":{"description":"Returns the current job state and customer-facing progress counters. Requires the `searches:read` scope when called with a scoped key.","operationId":"getSearch","parameters":[{"$ref":"#/components/parameters/JobID"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Search"}}},"description":"Search"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Get a search","tags":["Searches"]}},"/v1/searches/{id}/cancel":{"post":{"description":"Accepts a cancellation request for a cancellable job. Settlement is asynchronous: the finalize worker keeps and charges for results already produced, refunds the unused reservation, and then changes the job status to `canceled`. Poll the job until that terminal status is visible. Requires the `searches:write` scope when called with a scoped key.","operationId":"cancelSearch","parameters":[{"$ref":"#/components/parameters/JobID"}],"responses":{"202":{"content":{"application/json":{"schema":{"properties":{"canceling":{"const":true,"type":"boolean"}},"required":["canceling"],"type":"object"}}},"description":"Cancellation accepted; asynchronous settlement is still in progress."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"content":{"application/json":{"example":{"error":"not_cancellable"},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The job does not exist for this account, is already terminal, or is otherwise not cancellable."},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Cancel a search","tags":["Searches"]}},"/v1/searches/{id}/export":{"get":{"description":"Downloads all or selected leads as CSV, JSON, or Excel. The response is a file rather than a JSON envelope. Requires the `leads:export` scope when called with a scoped key.","operationId":"exportSearchLeads","parameters":[{"$ref":"#/components/parameters/JobID"},{"description":"Export file format.","in":"query","name":"format","schema":{"default":"csv","enum":["csv","json","xlsx"],"type":"string"}},{"description":"Optional comma-separated lead IDs to include.","in":"query","name":"leadIds","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Lead"},"type":"array"}},"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":{"schema":{"contentEncoding":"base64","type":"string"}},"text/csv":{"schema":{"type":"string"}}},"description":"Export file","headers":{"Content-Disposition":{"description":"Attachment filename.","schema":{"example":"attachment; filename=\"mapleads-design-agencies.csv\"","type":"string"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Export leads","tags":["Searches"]}},"/v1/searches/{id}/leads":{"get":{"description":"Returns a paginated slice of the job's extracted business leads. Enrichment fields depend on the account's current plan. Requires the `leads:read` scope when called with a scoped key.","operationId":"listSearchLeads","parameters":[{"$ref":"#/components/parameters/JobID"},{"description":"Maximum leads to return. Values above 1000 fall back to the default page size of 100.","in":"query","name":"limit","schema":{"default":100,"maximum":1000,"minimum":1,"type":"integer"}},{"description":"Number of leads to skip.","in":"query","name":"offset","schema":{"default":0,"minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Lead"},"type":"array"},"total":{"minimum":0,"type":"integer"}},"required":["data","total"],"type":"object"}}},"description":"Lead page"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"List leads for a search","tags":["Searches"]}},"/v1/usage":{"get":{"description":"Returns the authenticated account's access state, remaining credits, allowance, reset date, and effective API-key rate limit. Requires the `usage:read` scope when called with a scoped key.","operationId":"getUsage","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"}}},"description":"Usage summary"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Get usage and credits","tags":["Usage"]}},"/v1/webhooks":{"get":{"description":"Lists the webhook endpoints owned by the authenticated user. Requires the `webhooks:read` scope when called with a scoped key.","operationId":"listWebhooks","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Webhook"},"type":"array"}},"required":["data"],"type":"object"}}},"description":"Webhook endpoints"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"List webhook endpoints","tags":["Webhooks"]},"post":{"description":"Registers an HTTPS endpoint. The signing secret is returned once. Private, loopback, and otherwise unsafe destinations are rejected. Requires the `webhooks:write` scope when called with a scoped key.","operationId":"createWebhook","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedWebhook"}}},"description":"Webhook endpoint created"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Create a webhook endpoint","tags":["Webhooks"]}},"/v1/webhooks/test":{"post":{"description":"Delivers a `webhook.test` event to one of your endpoints right now and reports what actually happened, so you can confirm the URL is reachable and your signature check works without waiting for a real search.\n\nThe request is byte-for-byte the same shape as a real delivery: same `X-Mapleads-Signature` (HMAC-SHA256 of the body with your endpoint secret), same headers, same timeout, same outbound IP pinning.\n\nThe event type is `webhook.test`, never a fake `search.completed` — so your receiver can ignore it instead of running real business logic.\n\n⚠️ A failed delivery still returns `200`: you asked us to test, and we are reporting the result. Check `ok` in the body, not the HTTP status. `400` means we could not send at all (for example the URL no longer resolves to a public address).\n\nRequires the `webhooks:write` scope — it changes nothing here, but it makes our servers call the URL you configured.","operationId":"testWebhook","requestBody":{"content":{"application/json":{"schema":{"properties":{"endpointId":{"description":"Id of the endpoint to test.","type":"string"}},"required":["endpointId"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"deliveryId":{"description":"Value sent in X-Mapleads-Delivery.","type":"string"},"error":{"description":"Transport-level failure (DNS, refused, timeout, TLS). Absent when a response came back.","type":"string"},"ok":{"description":"True when your endpoint replied 2xx.","type":"boolean"},"status":{"description":"HTTP status your endpoint returned. Absent when the request never got a response.","type":"integer"}},"required":["ok","deliveryId"],"type":"object"}}},"description":"We sent the delivery. `ok` tells you whether your endpoint accepted it."},"400":{"description":"We could not send at all — for example the URL no longer resolves to a public address."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No enabled endpoint with that id belongs to you."}},"summary":"Send a test delivery","tags":["Webhooks"]}},"/v1/webhooks/{id}":{"delete":{"description":"Deletes a webhook endpoint. Requires the `webhooks:write` scope when called with a scoped key.","operationId":"deleteWebhook","parameters":[{"$ref":"#/components/parameters/ResourceID"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"deleted":{"const":true,"type":"boolean"}},"required":["deleted"],"type":"object"}}},"description":"Webhook endpoint deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}},"summary":"Delete a webhook endpoint","tags":["Webhooks"]}}},"security":[{"bearer":[]},{"apiKeyHeader":[]}],"servers":[{"description":"Production","url":"https://api.mapleads.ai"}],"tags":[{"description":"Run one query against a source map and turn the results into your leads. Asynchronous: create, poll, then read or export what it produced.","name":"Searches"},{"description":"The shared dataset every account searches. Browsing is free and never returns a phone number, an email address or a URL; unlocking a row buys those into your leads for one credit.","name":"Businesses"},{"description":"Everything this account owns, however it got here — from a search or from unlocking a dataset row. Reading and exporting your own leads is free.","name":"Leads"},{"description":"Receive search completion and failure events instead of polling.","name":"Webhooks"},{"description":"Inspect the authenticated account's plan, credit balance, and rate limit.","name":"Usage"},{"description":"Create and manage scoped API credentials.","name":"API keys"}]}
