Errors

Error response shapes and how to handle them.

Every JSON error carries a human-readable message: key off it. Two
responses are not JSON. The 401 is a bare text/plain string, because the
API key is validated at Sovrn's gateway before the request reaches the
service, and a 500 is the plain text Internal Server Error. Handle these
cases:

StatusMeaningBody
400Validation failedEither the validation shape, whose message starts with the path of the field, or { "message": "<reason>" }. See Validation errors.
400The deal request id in the path is malformedThe validation shape, with the message params/id Invalid deal request ID
401API key missing, invalid, or malformedtext/plain, not JSON. One of three strings: api key not provided (no x-api-key header), invalid api key (unknown, revoked, or expired key), invalid api key syntax (malformed key)
403Key valid but not permitted{ "message": "Insufficient scope" }: a read-only key sent a write (POST or PATCH, a dry run included). It is checked before the body is validated, so it arrives even when the body is invalid too. { "message": "Partner not authorized for this API" }: the account is not enabled for the API. On a write it is checked after the body is validated, so an invalid body gets its 400 first.
404No deal request with that id on your account{ "message": "Deal request not found" }
415A PATCH was sent as application/merge-patch+json{ "statusCode": 415, "code": "FST_ERR_CTP_INVALID_MEDIA_TYPE", "error": "Unsupported Media Type", "message": "Unsupported Media Type" }. Send a PATCH body as application/json.
429The key went over the rate limitJSON, with a message such as Rate limit exceeded, retry in 45 seconds. Wait that long before sending again.
500The downstream deal system could not be reached or did not answer in time, or another unexpected failuretext/plain, not JSON: Internal Server Error. See Retrying.
502The downstream deal system rejected the request, or returned a result the API could not use{ "message": "…", "upstream": "vulcan" }. See Retrying.

Retrying

🚧

A 5xx does not mean nothing happened

A 502 or a 500 from a write does not tell you the request left nothing
behind, so what to do depends on the request:

  • Creating a deal (POST /deal-requests): the deal may already exist
    on Sovrn's side with no deal request recorded for it. Check with your
    Sovrn representative before you retry, or you may create it twice.
  • Editing a deal (PATCH /deal-requests/{id}): if the request set a
    status, the other fields may already be applied. Re-send the same
    request until it returns 200; a PATCH sets the same values each time
    it is sent.
  • An archived deal refuses every edit with a 502, so re-sending will
    not help (see Deal lifecycle).
    If you were archiving the deal and the re-send keeps returning 502, read
    the deal: if it reads archived, the archive has already landed.

Do not JSON.parse a 401 or a 500: their bodies are plain strings.

Validation errors

A 400 arrives in one of two shapes.

When a field fails the request's schema, the body carries statusCode,
code and error beside a message that starts with the path of the field
(body/…, params/… or querystring/…):

{
  "statusCode": 400,
  "code": "FST_ERR_VALIDATION",
  "error": "Bad Request",
  "message": "body/commercial/takeRate takeRate is a decimal fraction; expected 0 ≤ x ≤ 0.85"
}

When several fields fail, their messages arrive together in the one
message, separated by commas. To recognize a particular error, match on the
text after the path.

A rule the API checks against the deal as stored, such as editing the buyers
of an open deal or a DV360 requirement on a PATCH, returns message only:

{
  "message": "buyers cannot be edited on an open deal; create a new deal request instead"
}

Messages you may see, shown without their path: takeRate is a decimal fraction; expected 0 ≤ x ≤ 0.85, startDate must be today or later (UTC),
runOfNetwork and targeting are mutually exclusive; provide one or the other,
at most 25 targeting groups per deal request, unknown audience segment id(s): …, country code(s) not available for targeting: …,
device.deviceType value(s) not available for targeting: …,
publisher id(s) not valid: …,
schedule.endDate is required for DV360 buyers (see
Demand partners).

Validation is not uniform across targeting values

Only device.geo.country, device.geo.region, device.language,
device.deviceType, audience.segmentids, content.categorization.v3,
content.categorization.v1 and publisher.id values are checked when you
submit
, on a dry run too: every attribute whose valueFormat is
enumerated, except adUnit.size, plus publisher.id. A value that is not
on its attribute's
GET /targeting-attributes/{attribute}/values
list returns a 400 listing the values in question, and so does a publisher
id that is not a positive whole number up to 2147483647. A 2-letter country
code such as US is not rejected: it is converted to its 3-letter code, USA
(see Targeting).

Worth knowing when debugging a deal that was accepted but isn't delivering:
the other free-text attributes accept any value of the right JSON type, and
so do ad sizes, although they have a list. A malformed ad-size token produces
a deal that is created successfully and then never matches traffic, with no
error at any point. See Targeting for the exact format each
attribute expects.

Open deals

Two 400 messages are specific to type: open deals (see
Deal lifecycle), and both are exact strings you
can match on:

Whenmessage
A buyer on an open deal request lists a seatopen deals are non-seat: omit seatIds — each buyer carries the DSP only
A PATCH on an existing open deal includes buyersbuyers cannot be edited on an open deal; create a new deal request instead

The first arrives in the validation shape, prefixed with the field path
(body/buyers/0/seatIds …); the second is a plain { "message": "…" } body.
Neither is retryable as sent: fix the request.

Every buyer in a PATCH must list at least one seat, and that is checked
before the open-deal rule. So a PATCH on an open deal whose buyer has no
seatIds gets the seat message first,
body/buyers/0/seatIds Invalid input: expected array, received undefined,
and not the second message above.

Setting a status

Two more 400 messages come from PATCHing a status (see
Deal lifecycle):

Whenmessage
The value is outside the four a curator may setbody/status Invalid option: expected one of "active"|"paused"|"archived"|"scheduled"
Every buyer on the deal is on a DSP that manages its own statusstatus is not settable on this deal: every buyer is on a DSP that manages deal status through its own integration (dv360)

The second names the DSPs on the deal that blocked the change, so it varies
with the deal. Neither is retryable as sent. To avoid the second, read the
deal's statusSettable first and offer a status change only when it is
true; see
Deal lifecycle.


Did this page help you?