{"openapi":"3.1.0","info":{"title":"GeoSwap API","version":"1","description":"Send conversions from your CRM or server and link funnel stages. Deletion of a person's conversions is on request: email support@geoswap.co.\n\nBatching: A batch is written in chunks of 100. If a later chunk fails, earlier chunks may already be committed and the request returns 500 — re-send the whole batch. That's safe: GeoSwap matches on event + external_id, so already-written items are just updated again.\n\nRate limits: Per API key: 600 requests and 30,000 conversions per minute, in fixed one-minute windows (a batch counts each conversion). Over the limit you get 429 rate_limited with a Retry-After header.\n\nNot available: Read conversions or attribution reports back over the API. Delete a single conversion by id, or erase a person's data through the API: email support@geoswap.co and GeoSwap erases it for you. Manage links, redirects or other rules. Send conversions to ad platforms (Google, Meta) for you. Store raw email, phone or address: they're hashed on receipt. Authenticate with OAuth: API keys only.\n\nVersioning: /v1 only changes additively; breaking changes ship as /v2 with at least 6 months' notice. The v1 base https://geoswap.co/api/v1 keeps working as an alias.","contact":{"email":"support@geoswap.co","url":"https://geoswap.co/docs/api"}},"servers":[{"url":"https://api.geoswap.co/v1"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Workspace"},{"name":"Conversions"}],"paths":{"/workspace":{"get":{"operationId":"getWorkspace","tags":["Workspace"],"summary":"Who am I? Verify a key","x-required-scope":"workspace:read","responses":{"200":{"description":"The key's workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceResponse"}}}},"401":{"description":"missing_api_key | invalid_api_key","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}},"WWW-Authenticate":{"description":"RFC 6750: Bearer realm=\"GeoSwap API\", with error=\"invalid_token\" when the key is invalid or revoked.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"workspace_suspended | insufficient_scope","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}},"WWW-Authenticate":{"description":"RFC 6750: present (Bearer realm=\"GeoSwap API\", error=\"insufficient_scope\", scope=\"…\") only for insufficient_scope, not workspace_suspended.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"workspace_not_found","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds until the rate-limit window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/conversions":{"post":{"operationId":"createConversion","tags":["Conversions"],"summary":"Send one conversion (create or update)","x-required-scope":"conversions:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionInput"},"example":{"event":"qualified","external_id":"006Hs00001AbCdE","parent_external_id":"00QHs00002XyZqR","occurred_at":"2026-10-12T14:03:00Z","visitor_id":"Xb3k9_Qm2LpA7vR1.20261003","email":"jane@example.com","value":1200,"currency":"USD","tags":{"market":"co"}}}}},"responses":{"200":{"description":"Updated or unchanged (result says which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionResponse"}}}},"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionResponse"}}}},"400":{"description":"invalid_json | query_not_allowed","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"missing_api_key | invalid_api_key","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}},"WWW-Authenticate":{"description":"RFC 6750: Bearer realm=\"GeoSwap API\", with error=\"invalid_token\" when the key is invalid or revoked.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"workspace_suspended | insufficient_scope","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}},"WWW-Authenticate":{"description":"RFC 6750: present (Bearer realm=\"GeoSwap API\", error=\"insufficient_scope\", scope=\"…\") only for insufficient_scope, not workspace_suspended.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"payload_too_large","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed | occurred_at_out_of_range","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds until the rate-limit window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/conversions/batch":{"post":{"operationId":"createConversionBatch","tags":["Conversions"],"summary":"Send up to 500 conversions","x-required-scope":"conversions:write","description":"A batch is written in chunks of 100. If a later chunk fails, earlier chunks may already be committed and the request returns 500 — re-send the whole batch. That's safe: GeoSwap matches on event + external_id, so already-written items are just updated again.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionBatchInput"},"example":{"conversions":[{"event":"qualified","external_id":"006Hs00001AbCdE","parent_external_id":"00QHs00002XyZqR","occurred_at":"2026-10-12T14:03:00Z","visitor_id":"Xb3k9_Qm2LpA7vR1.20261003","email":"jane@example.com","value":1200,"currency":"USD","tags":{"market":"co"}},{"event":"purchase","external_id":"order-1001","occurred_at":"2026-10-12T15:20:00Z","visitor_id":"Pq7Lm2Xr9Ka0Zt3W","value":49.99,"currency":"USD","tags":{"site":"diara","market":"in"}}]}}}},"responses":{"200":{"description":"Per-item results in input order. Item errors don't fail the batch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResponse"}}}},"400":{"description":"invalid_json | query_not_allowed","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"missing_api_key | invalid_api_key","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}},"WWW-Authenticate":{"description":"RFC 6750: Bearer realm=\"GeoSwap API\", with error=\"invalid_token\" when the key is invalid or revoked.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"workspace_suspended | insufficient_scope","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}},"WWW-Authenticate":{"description":"RFC 6750: present (Bearer realm=\"GeoSwap API\", error=\"insufficient_scope\", scope=\"…\") only for insufficient_scope, not workspace_suspended.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"payload_too_large","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_failed (the envelope; item errors are in results)","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds until the rate-limit window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal_error","headers":{"X-Request-ID":{"description":"Same as error.request_id. Quote it to support.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A workspace API key (gs_live_…) from Settings → API keys. The bare key without 'Bearer ' is accepted too."}},"schemas":{"Address":{"type":"object","properties":{"line1":{"type":"string","minLength":1,"maxLength":200,"description":"Street line including any unit, e.g. \"123 N Main St Apt 4B\". There is no line2: append the apartment or suite to line1."},"zip":{"type":"string","minLength":1,"maxLength":16,"description":"ZIP or postal code."},"country":{"description":"ISO 3166-1 alpha-2. Defaults to the conversion's country, else US.","type":"string","pattern":"^[A-Za-z]{2}$"}},"required":["line1","zip"],"additionalProperties":false,"description":"A postal address. Normalized and hashed on receipt; never stored raw."},"ConversionInput":{"type":"object","properties":{"event":{"type":"string","pattern":"^[A-Za-z0-9_]{1,40}$","description":"Stage name: lead, qualified, signed, purchase… Letters, digits and _, up to 40; stored lower-case."},"external_id":{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[^\\x00-\\x1f\\x7f]+$"},{"type":"integer","minimum":0,"maximum":9007199254740991}],"description":"Your id for this conversion (lead id, order id). A JSON number is accepted too and stored as its decimal string. Unique per event: re-sending the same event + external_id updates it."},"parent_external_id":{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[^\\x00-\\x1f\\x7f]+$"},{"type":"integer","minimum":0,"maximum":9007199254740991}],"description":"The external_id of an earlier stage of the same journey (e.g. the lead behind this deal; a JSON number is accepted too). Links stages into one chain; the parent may arrive later."},"occurred_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"integer","minimum":1000000000000,"maximum":100000000000000},{"type":"string","pattern":"^\\d{13}$"}],"description":"When it happened: ISO 8601 with a time zone (2026-10-12T14:03:00Z) or epoch milliseconds. Any age: backfills are welcome, but conversions older than 90 days can't be matched to visits. Not in the future."},"visitor_id":{"type":"string","pattern":"^([A-Za-z0-9_-]{16})(?:\\.\\d{8})?$","description":"The gs_vid cookie value (or geoswap.ids().vid) captured on your site, e.g. from a hidden gs_vid form field."},"click_id":{"description":"The GeoSwap click id (the gs_c hidden field).","type":"string","pattern":"^[0-9A-Za-z]{12}$"},"contact_id":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[^\\x00-\\x1f\\x7f]+$","description":"Your CRM contact id. Also matches the gs_r recipient id on per-recipient links."},"email":{"type":"string","maxLength":254,"description":"Raw email. Normalized (trim, lower-case) and SHA-256 hashed on receipt; never stored or logged raw."},"phone":{"type":"string","maxLength":32,"description":"Raw phone. Normalized to E.164 (needs a + prefix or `country`) and SHA-256 hashed; never stored raw."},"country":{"description":"ISO 3166-1 alpha-2 country of the customer; also the phone's default country.","type":"string","pattern":"^[A-Za-z]{2}$"},"address":{"$ref":"#/components/schemas/Address"},"value":{"description":"Value in currency units (49.99). Numeric strings are accepted.","anyOf":[{"type":"number","minimum":0,"maximum":1000000000},{"type":"string","pattern":"^\\d{1,10}(\\.\\d{1,6})?$"}]},"currency":{"description":"ISO 4217 code, e.g. USD.","type":"string","pattern":"^[A-Za-z]{3}$"},"hdyhau":{"description":"\"How did you hear about us?\" answer, e.g. from an SDR call.","type":"string","minLength":1,"maxLength":200,"pattern":"^[^\\x00-\\x1f\\x7f]+$"},"tags":{"description":"Up to 5 labels for reporting, e.g. {\"site\": \"diara\", \"market\": \"in\"}. Keys: a-z and _, up to 24; values up to 64 chars.","type":"object","propertyNames":{"type":"string","pattern":"^[a-z_]{1,24}$"},"additionalProperties":{"type":"string","maxLength":64},"maxProperties":5}},"required":["event","external_id","occurred_at"],"additionalProperties":false,"description":"One conversion. Fields you omit on a re-send keep their stored value. `null` or an empty string in an optional field means it wasn't sent (not a validation error)."},"ConversionBatchInput":{"type":"object","properties":{"conversions":{"minItems":1,"maxItems":500,"type":"array","items":{"$ref":"#/components/schemas/ConversionInput"},"description":"1 to 500 conversions, written in order."}},"required":["conversions"],"additionalProperties":false,"description":"A batch. Each item succeeds or fails on its own."},"ErrorDetail":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"]},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable, lower snake_case. See /docs/api#errors."},"message":{"type":"string"},"request_id":{"type":"string","description":"Same as the X-Request-ID header. Quote it to support."},"details":{"description":"validation_failed only: which fields, never their values.","type":"array","items":{"$ref":"#/components/schemas/ErrorDetail"}}},"required":["code","message","request_id"]}},"required":["error"],"description":"Every error."},"ConversionResult":{"type":"object","properties":{"id":{"type":"string","description":"cnv_…"},"event":{"type":"string"},"external_id":{"type":"string"},"chain_id":{"type":"string","description":"chn_…: the journey this stage belongs to."},"result":{"type":"string","enum":["created","updated","unchanged"]},"warnings":{"description":"Identifiers that couldn't be used and were dropped.","type":"array","items":{"type":"string","enum":["email_invalid","phone_unparseable","address_unparseable"]}}},"required":["id","event","external_id","chain_id","result"]},"ConversionResponse":{"type":"object","properties":{"conversion":{"$ref":"#/components/schemas/ConversionResult"}},"required":["conversion"]},"BatchItemError":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"array","items":{"$ref":"#/components/schemas/ErrorDetail"}}},"required":["code","message"]},"BatchItemResult":{"anyOf":[{"type":"object","properties":{"index":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"ok":{"type":"boolean","const":true},"conversion":{"$ref":"#/components/schemas/ConversionResult"}},"required":["index","ok","conversion"]},{"type":"object","properties":{"index":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"ok":{"type":"boolean","const":false},"error":{"$ref":"#/components/schemas/BatchItemError"}},"required":["index","ok","error"]}]},"BatchResponse":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/BatchItemResult"},"description":"One per input item, in input order."},"summary":{"type":"object","properties":{"created":{"type":"integer","minimum":0,"maximum":9007199254740991},"updated":{"type":"integer","minimum":0,"maximum":9007199254740991},"unchanged":{"type":"integer","minimum":0,"maximum":9007199254740991},"failed":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["created","updated","unchanged","failed"]}},"required":["results","summary"]},"WorkspaceResponse":{"type":"object","properties":{"workspace":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"projectId":{"type":"string"},"status":{"type":"string"},"siteRestriction":{"type":"string","enum":["any","listed"]}},"required":["id","name","projectId","status","siteRestriction"]},"script":{"type":"object","properties":{"src":{"type":"string"},"snippet":{"type":"string"}},"required":["src","snippet"]},"sites":{"type":"array","items":{"type":"object","properties":{"host":{"type":"string"},"lastSeenAt":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["host","lastSeenAt"]}},"key":{"type":"object","properties":{"id":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}}},"required":["id","scopes"]}},"required":["workspace","script","sites","key"]}}}}