Guides

Errors

Every error has the same JSON envelope, a stable machine code and a request id. Here is every code, its HTTP status and what to do about it.

A failed call answers with a 4xx or 5xx status and this body:

{
  "error": {
    "code": "validation_failed",
    "message": "limit Too big: expected number to be <=100",
    "details": [
      { "field": "limit", "message": "Too big: expected number to be <=100", "code": "too_big" }
    ],
    "requestId": "19d815e9-2261-4a45-a3f2-0333e120eea5"
  }
}
FieldWhat it is
codeA stable snake_case code. Branch on this, never on message.
messageA human-readable explanation. It can change wording at any time.
detailsFor validation_failed, a list of { field, message, code }, one per invalid field. Otherwise extra context as an object, or null.
requestIdThe same value as the Koast-Request-Id response header. Quote it to support.

Every response, successful or not, carries the Koast-Request-Id header.

Codes

HTTPcodeMeaningWhat to do
400validation_failedA parameter or body field is missing, has the wrong type, is out of range, or isn't a known field. Also an invalid or expired cursor.Fix the request using details. Don't retry unchanged.
400idempotency_key_requiredlaunch-campaign or edit-live-campaign was sent without an Idempotency-Key header.Add one. See Idempotency.
401unauthenticatedNo credential, or one Koast doesn't recognise, or a revoked or expired key.Check the Authorization header and the key.
401audience_mismatchAn OAuth token was sent to a host or path outside the API, or the token was issued for the MCP server, not the API.Call https://api.koast.ai/v1 with a token issued for it, for example after koast login.
403plan_requiredThe organization isn't on an active Agency plan.See Access and plans.
403surface_disabledThe key has API access turned off.An owner or admin turns on API for the key.
403insufficient_scopeThe key lacks the scope this operation needs. details.requiredScope names it.Use a key with that scope.
403brand_not_allowedThe key is limited to other brands, or the OAuth user's role gives them only some brands.Use a credential that covers this brand.
403forbiddenThe credential can't reach this brand or resource, or its role doesn't allow the action.Check the brand id and the user's role.
403spend_ceiling_requiredA publishing call from a key with no spend ceiling.Set a publish limit on the key.
403spend_ceiling_exceededThe call would take the key past its spend ceiling. Nothing was published.Raise the limit or use another key.
404not_foundNo such endpoint, or no such resource in this brand.Check the path and ids.
409idempotency_conflictThe Idempotency-Key was already used with a different body, or the first request with it is still running.Use a new key for a new request, or wait and retry the same request.
409stale_readThe resource changed since you read it.Read it again, then retry.
409conflictThe request conflicts with the resource's current state, such as a test send to a disabled webhook endpoint ("Enable the endpoint first.").Read the message, fix, retry.
413validation_failedThe request body is larger than 5 MB.Send a smaller body.
422capability_unsupportedThe ad platform behind this brand doesn't support this operation or setting.Don't retry.
422account_ambiguousThe brand has more than one ad account and the call didn't say which.Pass the accountId.
422unprocessableThe request is valid but can't be carried out, like a 21st webhook endpoint.Read the message.
429rate_limitedToo many requests or too many in flight for your organization.Wait Retry-After seconds. See Rate limits.
500internal_errorSomething failed on Koast's side.Retry with backoff. Contact support with the requestId if it persists.
503unavailableA dependency couldn't answer, for example the spend ceiling couldn't be checked. Nothing was published.Retry shortly.
503webhooks_unavailableWebhooks aren't available in this environment.Contact support.
504timeoutThe request didn't finish within 30 seconds. It may still complete.Retry with the same Idempotency-Key.

New codes may be added within v1. Treat an unknown code by its HTTP status. See Versioning.

Examples

A field the endpoint doesn't know. Every endpoint rejects unknown query and body fields:

curl "https://api.koast.ai/v1/brands/1/launches?colour=blue" \
  -H "Authorization: Bearer $KOAST_API_KEY"
{
  "error": {
    "code": "validation_failed",
    "message": "colour is not a recognised field",
    "details": [
      { "field": "colour", "message": "is not a recognised field", "code": "unrecognized_key" }
    ],
    "requestId": "7afaef82-028e-4156-8a15-8d79211fc6ab"
  }
}

An endpoint that doesn't exist:

curl https://api.koast.ai/v1/campaigns \
  -H "Authorization: Bearer $KOAST_API_KEY"
{
  "error": {
    "code": "not_found",
    "message": "No endpoint matches GET /v1/campaigns.",
    "details": null,
    "requestId": "6e85b1a4-0f16-4162-b662-5f7dc6269cf5"
  }
}

A publish without an Idempotency-Key:

curl -X POST https://api.koast.ai/v1/brands/1/launches/cmv09p0ny000bvcdsyaeedbj7/publish \
  -H "Authorization: Bearer $KOAST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
{
  "error": {
    "code": "idempotency_key_required",
    "message": "POST /v1/brands/{brandId}/launches/{launchId}/publish requires an Idempotency-Key header.",
    "details": null,
    "requestId": "c0fdb4c6-9d6b-44f7-a838-2cee90c46318"
  }
}

Several invalid fields at once. details lists each one:

{
  "error": {
    "code": "validation_failed",
    "message": "level Invalid option: expected one of \"CAMPAIGN\"|\"ADSET\" (and 2 more)",
    "details": [
      { "field": "level", "message": "Invalid option: expected one of \"CAMPAIGN\"|\"ADSET\"", "code": "invalid_value" },
      { "field": "budgetType", "message": "Invalid option: expected one of \"daily\"|\"lifetime\"", "code": "invalid_value" },
      { "field": "amount", "message": "Invalid input: expected number, received undefined", "code": "invalid_type" }
    ],
    "requestId": "37b819fd-86b7-43ce-bea2-2af0de0a8823"
  }
}

A launch that fails validation, on POST /v1/brands/{brandId}/launches/{launchId}/publish with or without "confirm": true, gets 400 validation_failed. message counts the issues and details has one { field, message } per issue. field takes the form campaigns[<nodeId>].<setting>, adSets[<nodeId>].<setting> or ads[<nodeId>].<setting>, where <setting> is a dotted path such as budget.dailyAmount:

{
  "error": {
    "code": "validation_failed",
    "message": "Launch validation failed: 2 issues.",
    "details": [
      { "field": "adSets[as1].budget.dailyAmount", "message": "Daily budget must be at least $1.00 (100 cents)" },
      { "field": "ads[ad1].creativeId", "message": "A creative (image or video) is required" }
    ],
    "requestId": "5a0c7e21-3d4f-4b8a-9e61-0f2b7d9c1a34"
  }
}

Malformed JSON

A body that isn't valid JSON gets the same envelope, with 400 validation_failed:

{
  "error": {
    "code": "validation_failed",
    "message": "The request body is not valid JSON.",
    "details": null,
    "requestId": "8f1e714f-d55b-4adf-a6d1-cd7f70aa95ae"
  }
}

Send valid JSON with Content-Type: application/json.

On this page