{"openapi":"3.1.0","info":{"title":"Attribloom public runtime API","version":"1.0.0","description":"Public merchant-callable conversion and iOS attribution runtime. Account, tenant-management, OAuth, payout, internal provider-webhook, and authenticated dashboard endpoints are intentionally excluded."},"servers":[{"url":"https://api.attribloom.com"}],"tags":[{"name":"Conversions","description":"Signed conversion and refund postbacks."},{"name":"App Store","description":"First-launch binding and signed ASSN v2 forwarding."}],"paths":{"/v1/conversions":{"post":{"operationId":"createConversion","summary":"Report a conversion","description":"Signed server postback. externalId is the idempotency key within a surface. Retry network failures and 5xx responses with a fresh timestamp and signature. A replay returns status duplicate without another accrual.","security":[{"EaSurface":[],"EaTimestamp":[],"EaSignature":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionRequest"},"example":{"externalId":"order_12345","refCode":"CREATOR10","grossMinor":4999,"taxMinor":0,"currency":"USD","eventType":"sale","occurredAt":"2026-07-14T12:00:00.000Z"}}}},"responses":{"200":{"description":"Accepted, including duplicate or unattributed outcomes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionResponse"},"example":{"conversionId":"conv_123","status":"ok","attributed":true,"commissionMinor":500}}}},"400":{"description":"Malformed JSON or invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing headers, unknown surface, bad signature, or timestamp skew.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the response's retry-after interval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","message"],"properties":{"statusCode":{"const":429},"error":{"const":"Too Many Requests"},"message":{"type":"string"}}}}}},"503":{"description":"Conversion ingestion is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"BODY='{\"externalId\":\"order_12345\",\"refCode\":\"CREATOR10\",\"grossMinor\":4999,\"taxMinor\":0,\"currency\":\"USD\",\"eventType\":\"sale\",\"occurredAt\":\"2026-07-14T12:00:00.000Z\"}'\nTS=$(date +%s)\nSIG=$(printf '%s' \"$TS.$BODY\" | openssl dgst -sha256 -hmac \"$ATTRIBLOOM_SECRET\" -hex | sed 's/^.* /sha256=/')\ncurl --fail-with-body https://api.attribloom.com/v1/conversions \\\n  -H 'content-type: application/json' -H \"x-ea-surface: $ATTRIBLOOM_SURFACE_ID\" \\\n  -H \"x-ea-timestamp: $TS\" -H \"x-ea-signature: $SIG\" --data-binary \"$BODY\""}]}},"/v1/conversions/refund":{"post":{"operationId":"refundConversion","summary":"Report a full or partial refund","description":"Signed refund postback. For refund events, refundId makes replay idempotent. refundedMinor is an integer amount for this refund event, so partial refunds use the actual partial amount. Use refund_reversed only to reverse a previously reported refund; reversal replay safety is guarded by conversion state.","security":[{"EaSurface":[],"EaTimestamp":[],"EaSignature":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundRequest"},"example":{"externalId":"order_12345","refundId":"refund_67890","refundedMinor":2500,"currency":"USD"}}}},"responses":{"200":{"description":"Applied or already applied idempotently.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundResponse"},"example":{"conversionId":"conv_123","status":"ok"}}}},"400":{"description":"Malformed JSON or invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing headers, unknown surface, bad signature, or timestamp skew.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No conversion matches externalId on this surface.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The refund currency does not match the conversion currency.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the response's retry-after interval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","message"],"properties":{"statusCode":{"const":429},"error":{"const":"Too Many Requests"},"message":{"type":"string"}}}}}},"503":{"description":"Conversion ingestion is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"BODY='{\"externalId\":\"order_12345\",\"refundId\":\"refund_67890\",\"refundedMinor\":2500,\"currency\":\"USD\"}'\nTS=$(date +%s)\nSIG=$(printf '%s' \"$TS.$BODY\" | openssl dgst -sha256 -hmac \"$ATTRIBLOOM_SECRET\" -hex | sed 's/^.* /sha256=/')\ncurl --fail-with-body https://api.attribloom.com/v1/conversions/refund \\\n  -H 'content-type: application/json' -H \"x-ea-surface: $ATTRIBLOOM_SURFACE_ID\" \\\n  -H \"x-ea-timestamp: $TS\" -H \"x-ea-signature: $SIG\" --data-binary \"$BODY\""}]}},"/v1/app-store/bind":{"post":{"operationId":"bindAppStoreInstall","summary":"Mint an appAccountToken from a deterministic referral","description":"Send signedClickId or refCode on first launch. No HMAC header is required. Persist the returned token and reuse it on every StoreKit 2 purchase.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BindRequest"},"example":{"signedClickId":"<signed-click-id>"}}}},"responses":{"200":{"description":"Binding minted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BindResponse"},"example":{"appAccountToken":"7b30c25d-e7cc-4d19-b2ab-bc0d7ce3af5b","offerCode":"CREATOR50"}}}},"400":{"description":"Missing, unknown, invalid, or expired referral input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the response's retry-after interval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","message"],"properties":{"statusCode":{"const":429},"error":{"const":"Too Many Requests"},"message":{"type":"string"}}}}}},"503":{"description":"Click attribution is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl --fail-with-body https://api.attribloom.com/v1/app-store/bind \\\n  -H 'content-type: application/json' --data-binary '{\"signedClickId\":\"<signed-click-id>\"}'"}]}},"/v1/app-store/bind/deferred":{"post":{"operationId":"bindAppStoreInstallDeferred","summary":"Mint an appAccountToken from a deferred IP match","description":"First-launch fallback that searches the preceding six hours for a click on this surface from the same privacy-preserving IP HMAC. No HMAC header is required.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeferredBindRequest"},"example":{"surfaceId":"<surface-id>"}}}},"responses":{"200":{"description":"Binding minted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BindResponse"},"example":{"appAccountToken":"7b30c25d-e7cc-4d19-b2ab-bc0d7ce3af5b","offerCode":"CREATOR50"}}}},"400":{"description":"Malformed JSON or invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No matching recent click.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the response's retry-after interval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","message"],"properties":{"statusCode":{"const":429},"error":{"const":"Too Many Requests"},"message":{"type":"string"}}}}}},"503":{"description":"Click attribution is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl --fail-with-body https://api.attribloom.com/v1/app-store/bind/deferred \\\n  -H 'content-type: application/json' --data-binary '{\"surfaceId\":\"<surface-id>\"}'"}]}},"/v1/app-store/assn/{surfaceId}":{"post":{"operationId":"forwardAppStoreNotification","summary":"Forward an App Store Server Notification v2","description":"Sign the exact JSON bytes with this app's one-time forwarding secret. Delivery is idempotent by Apple's notification UUID. Retry network failures and 5xx responses with backoff and a fresh timestamp/signature.","security":[{"EaTimestamp":[],"EaSignature":[]}],"parameters":[{"name":"surfaceId","in":"path","required":true,"schema":{"type":"string","minLength":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssnRequest"},"example":{"signedPayload":"<apple-jws-verbatim>"}}}},"responses":{"200":{"description":"Verified and classified. Inspect status; do not retry a 200.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssnResponse"},"example":{"status":"ok"}}}},"400":{"description":"Malformed body, invalid Apple JWS, or bundle mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid forwarding HMAC, or timestamp skew.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The binding belongs to a different surface.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the response's retry-after interval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","message"],"properties":{"statusCode":{"const":429},"error":{"const":"Too Many Requests"},"message":{"type":"string"}}}}}},"503":{"description":"The App Store forwarding integration is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"BODY='{\"signedPayload\":\"<apple-jws-verbatim>\"}'\nTS=$(date +%s)\nSIG=$(printf '%s' \"$TS.$BODY\" | openssl dgst -sha256 -hmac \"$ATTRIBLOOM_SECRET\" -hex | sed 's/^.* /sha256=/')\ncurl --fail-with-body https://api.attribloom.com/v1/app-store/assn/$ATTRIBLOOM_SURFACE_ID \\\n  -H 'content-type: application/json' -H \"x-ea-timestamp: $TS\" \\\n  -H \"x-ea-signature: $SIG\" --data-binary \"$BODY\""}]}},"/v1/app-store/assn":{"post":{"operationId":"forwardAppStoreNotificationLegacy","summary":"Forward to the deprecated single-app ASSN shim","deprecated":true,"description":"Back-compatibility endpoint for the legacy configured app. New integrations must use /v1/app-store/assn/{surfaceId}.","security":[{"EaTimestamp":[],"EaSignature":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssnRequest"},"example":{"signedPayload":"<apple-jws-verbatim>"}}}},"responses":{"200":{"description":"Verified and classified. Inspect status; do not retry a 200.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssnResponse"},"example":{"status":"ok"}}}},"400":{"description":"Malformed body, invalid Apple JWS, or bundle mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid forwarding HMAC, or timestamp skew.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The binding belongs to a different surface.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the response's retry-after interval.","content":{"application/json":{"schema":{"type":"object","required":["statusCode","error","message"],"properties":{"statusCode":{"const":429},"error":{"const":"Too Many Requests"},"message":{"type":"string"}}}}}},"503":{"description":"The App Store forwarding integration is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"BODY='{\"signedPayload\":\"<apple-jws-verbatim>\"}'\nTS=$(date +%s)\nSIG=$(printf '%s' \"$TS.$BODY\" | openssl dgst -sha256 -hmac \"$ATTRIBLOOM_SECRET\" -hex | sed 's/^.* /sha256=/')\ncurl --fail-with-body https://api.attribloom.com/v1/app-store/assn \\\n  -H 'content-type: application/json' -H \"x-ea-timestamp: $TS\" \\\n  -H \"x-ea-signature: $SIG\" --data-binary \"$BODY\""}]}}},"components":{"securitySchemes":{"EaSurface":{"type":"apiKey","in":"header","name":"x-ea-surface","description":"Surface ID whose postback secret signs this request."},"EaTimestamp":{"type":"apiKey","in":"header","name":"x-ea-timestamp","description":"Unix time in seconds. Attribloom accepts at most 300 seconds of skew."},"EaSignature":{"type":"apiKey","in":"header","name":"x-ea-signature","description":"sha256=<lowercase hex HMAC-SHA256(secret, timestamp + '.' + exact raw request body)>."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"ConversionRequest":{"type":"object","required":["externalId","grossMinor","currency","eventType","occurredAt"],"properties":{"clickId":{"type":"string"},"refCode":{"type":"string"},"externalId":{"type":"string","minLength":1},"grossMinor":{"type":"integer","minimum":1},"taxMinor":{"type":"integer","minimum":0,"default":0},"currency":{"type":"string","minLength":3,"maxLength":3,"example":"USD"},"eventType":{"type":"string","minLength":1,"example":"sale"},"isTrial":{"type":"boolean","default":false},"occurredAt":{"type":"string","format":"date-time"},"converterIpHmac":{"type":"string"}}},"ConversionResponse":{"type":"object","required":["conversionId","status","attributed","commissionMinor"],"properties":{"conversionId":{"type":"string"},"status":{"type":"string","enum":["ok","duplicate","unattributed","review"]},"attributed":{"type":"boolean"},"commissionMinor":{"type":"integer"}}},"RefundRequest":{"type":"object","required":["externalId","refundId","refundedMinor","currency"],"properties":{"externalId":{"type":"string","minLength":1},"refundId":{"type":"string","minLength":1},"refundedMinor":{"type":"integer","minimum":1},"currency":{"type":"string","minLength":3,"maxLength":3,"example":"USD"},"eventType":{"type":"string","enum":["refund","refund_reversed"],"default":"refund"}}},"RefundResponse":{"type":"object","required":["conversionId","status"],"properties":{"conversionId":{"type":"string"},"status":{"const":"ok"}}},"BindRequest":{"type":"object","properties":{"signedClickId":{"type":"string","minLength":1},"refCode":{"type":"string","minLength":1}},"anyOf":[{"required":["signedClickId"]},{"required":["refCode"]}],"description":"Supply at least one of signedClickId or refCode. If both are supplied, Attribloom attempts signedClickId and ignores refCode."},"DeferredBindRequest":{"type":"object","required":["surfaceId"],"properties":{"surfaceId":{"type":"string","minLength":1}}},"BindResponse":{"type":"object","required":["appAccountToken"],"properties":{"appAccountToken":{"type":"string","format":"uuid"},"offerCode":{"type":"string"}}},"AssnRequest":{"type":"object","required":["signedPayload"],"properties":{"signedPayload":{"type":"string","minLength":1,"maxLength":65536}}},"AssnResponse":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["ok","duplicate","unattributed","ignored","ignored_non_production","trial"]}}}}},"x-attribloom-hmac":{"timestampSkewSeconds":300,"construction":"sha256=lowercaseHex(HMAC-SHA256(secret, utf8(timestamp + '.') || exactRawBodyBytes))","retry":"Use a fresh timestamp and signature for each retry. Conversion externalId, refundId, and Apple notification UUID provide operation-specific idempotency."},"x-attribloom-asv-errors":[{"code":"ASV-100","title":"No API key on file","cause":"Attribloom cannot ask Apple to send the test notification without your App Store Server API key.","fix":"Add your key above, or use the advanced 'run the test yourself' option below."},{"code":"ASV-201","title":"Apple rejected the key","cause":"Apple returned an authorization error for your App Store Server API key.","fix":"Re-check the Issuer ID, Key ID, and that the .p8 is the matching In-App Purchase key. Click Replace, re-enter it, and Verify again."},{"code":"ASV-202","title":"Apple is rate-limiting","cause":"Apple is throttling test-notification requests for this key right now.","fix":"Wait a minute, then click Verify again."},{"code":"ASV-203","title":"Could not reach Apple","cause":"Attribloom could not reach Apple's App Store Server API.","fix":"Try Verify again shortly. If it keeps failing, Apple's API may be having an outage."},{"code":"ASV-301","title":"Nothing reached Attribloom","cause":"Apple sent the test, but no notification arrived at your Forwarding URL within 15 minutes.","fix":"Your server must forward Apple's notification to the Forwarding URL, signed with your forwarding secret (see the Setup guide code). Apple cannot post to it directly."},{"code":"ASV-302","title":"Arrived but not verified","cause":"Something reached your Forwarding URL, but no valid notification completed verification.","fix":"Check the most recent attempt below, and confirm the bundle ID and environment match this app."},{"code":"ASV-401","title":"Apple signature failed","cause":"A notification arrived, but Apple's signature (JWS) on it did not verify.","fix":"Forward Apple's raw signedPayload exactly as received. Do not re-encode, reformat, or wrap it."},{"code":"ASV-402","title":"Bundle ID mismatch","cause":"A notification arrived for a different bundle ID than this app is registered with.","fix":"Confirm this app's bundle ID matches the app whose notifications you are forwarding."},{"code":"ASV-403","title":"Wrong environment","cause":"A notification arrived from the other Apple environment (Sandbox vs Production) than registered.","fix":"Set this app's environment to match where the notification came from, then Verify again."},{"code":"ASV-404","title":"Forwarding signature failed","cause":"A request reached the Forwarding URL, but its forwarding signature was missing or wrong.","fix":"Sign the forward with your forwarding secret and send the x-ea-timestamp and x-ea-signature headers (use the Setup guide forwarder code). Apple cannot post directly."}]}