Guides and reference
Standard responses
Every answer carries status, reason and code from one catalog. The tables below are generated from it.
Every JSON answer carries the same fields (the one exception: an unknown path answers a plain {"error": "Not found"}):
| Field | What it is |
status | The outcome, always one of: accepted, saved, pending, held, rejected, invalid, failed. |
reason | A short sentence-case explanation: Missing HS grad year, Not eligible - Program, Duplicate - Client. |
code | A stable upper-case code. Branch on it; it never changes meaning. |
retryable | Whether the same request may succeed later without a change. |
field / fields | The field(s) at fault, when there are any. |
level | For an eligibility refusal: where it applied (offer, program, bucket, campaign, affiliate, buyer). |
source | For DUPLICATE: icon (ICON already has the lead) or buyer (the school's system said duplicate). |
#Statuses
| status | Meaning |
accepted | The request succeeded: a lead was sold, or a read answered. |
saved | The lead was stored; nothing was sold. |
pending | The buyer has not decided yet. |
held | A test lead, held by ICON before any buyer received it. |
rejected | A valid request that ICON or the buyer declined (eligibility, cap, duplicate, …). |
invalid | The request must change before it can succeed (a missing or bad field, consent, authentication). |
failed | Nothing was decided (a timeout, a buyer or platform error); it may be retried. |
#Reasons
A reason ends in - Client when the school's own system decided, and in - <Level> (- Offer, - Program, …) when ICON's rules at that level decided. A reason names a field when one is at fault: Invalid ZIP code. Reasons never carry the buyer's own words, internal rule names, limits or ids.
#Codes
The HTTP column lists every status a code can be served with. Decisions about a lead (accepted, refused, held) come with HTTP 200: read status and code.
#Success
| code | status | Default reason | Retryable | HTTP |
OK | accepted | OK | no | 200 |
ACCEPTED | accepted | Lead accepted | no | 200 |
SAVED | saved | Lead saved | no | 200 |
PENDING | pending | Pending - Client | no | 200, 202 |
TEST_LEAD_HELD | held | Test lead held | no | 200 |
#Authentication and access
| code | status | Default reason | Retryable | HTTP |
AUTH_REQUIRED | invalid | Missing API key | no | 401 |
AUTH_INVALID | invalid | Invalid API key | no | 401 |
FORBIDDEN | invalid | Not permitted | no | 403 |
NOT_FOUND | invalid | Not found | no | 200, 400, 404 |
#Request
| code | status | Default reason | Retryable | HTTP |
METHOD_NOT_ALLOWED | invalid | Method not allowed | no | 405 |
MALFORMED_REQUEST | invalid | Malformed request | no | 400, 409 |
REQUIRED_FIELD_MISSING | invalid | Missing required field | no | 200, 400, 422 |
INVALID_FIELD | invalid | Invalid field | no | 200, 400, 422 |
BOT_CHECK_FAILED | invalid | Bot check failed | no | 400 |
RATE_LIMITED | failed | Too many requests | yes | 429 |
#Eligibility
| code | status | Default reason | Retryable | HTTP |
NOT_ELIGIBLE | rejected | Not eligible | no | 200 |
OFFER_UNAVAILABLE | rejected | Offer unavailable | no | 200, 400, 404, 410 |
#Caps
| code | status | Default reason | Retryable | HTTP |
CAP_REACHED | rejected | Cap reached | no | 200 |
CAP_UNAVAILABLE | failed | Cap check unavailable | yes | 200, 503 |
#Consent
| code | status | Default reason | Retryable | HTTP |
CONSENT_REQUIRED | invalid | Missing TCPA consent | no | 200, 400 |
CONSENT_INVALID | invalid | Invalid TCPA consent | no | 400, 404, 409 |
#Duplicate
| code | status | Default reason | Retryable | HTTP |
DUPLICATE | rejected | Duplicate - Offer | no | 200, 409 |
#Buyer decision
| code | status | Default reason | Retryable | HTTP |
BUYER_REJECTED | rejected | Rejected - Client | no | 200, 502 |
BUYER_INVALID_FIELD | invalid | Invalid field - Client | no | 200, 502 |
BUYER_NOT_ELIGIBLE | rejected | Not eligible - Client | no | 200, 502 |
BUYER_ERROR | failed | Error - Client | no | 200, 502 |
#Transport
| code | status | Default reason | Retryable | HTTP |
TIMEOUT | failed | Timeout - Client | yes | 200, 504 |
| code | status | Default reason | Retryable | HTTP |
UNAVAILABLE | failed | Temporarily unavailable | yes | 200, 503 |
INTERNAL_ERROR | failed | Internal error | no | 500 |
#Buyer reasons
When a school's system refuses a lead, ICON translates its answer into one of these standardized reasons (a school that says it is full answers CAP_REACHED, Cap reached, like any other cap):
| reason | code | field |
| Rejected - Client | BUYER_REJECTED | — |
| Duplicate - Client | DUPLICATE | — |
| Location not served - Client | BUYER_NOT_ELIGIBLE | zip |
| Age not eligible - Client | BUYER_NOT_ELIGIBLE | date_of_birth |
| Education not eligible - Client | BUYER_NOT_ELIGIBLE | education_level |
| HS grad year not eligible - Client | BUYER_NOT_ELIGIBLE | grad_year |
| Program not offered - Client | BUYER_NOT_ELIGIBLE | program_id |
| Invalid phone number - Client | BUYER_INVALID_FIELD | phone |
| Invalid email - Client | BUYER_INVALID_FIELD | email |
| Invalid ZIP code - Client | BUYER_INVALID_FIELD | zip |
| Invalid TrustedForm token - Client | BUYER_INVALID_FIELD | trustedform_cert_url |
| Consent not accepted - Client | BUYER_REJECTED | — |
| Test lead - Client | BUYER_REJECTED | — |
The field column uses ICON's short field names (see the field dictionary). A buyer answer ICON cannot map reads Rejected - Client.