Error codes
Every error code the API returns, what it means and what to do about it.
When a request fails, the response body is a problem object with a stable code. Check code in your program; the detail text is for people and may change. The API overview shows the full shape.
JSON
{
"type": "https://docs.blackgumlabs.com/errors/alias.taken",
"title": "Conflict",
"status": 409,
"detail": "“spring-sale” is already in use on this domain. Try another back-half.",
"code": "alias.taken",
"traceId": "00-68e8a002645c783ee9ceb0fb177c10ea-b45bb3b99037b204-00"
}
Errors without a code:
| Status | When | What to do |
|---|---|---|
| 401 | The Authorization header is missing, isn't a bgl_ key, or the key is wrong, revoked or expired. The body is empty. | Check the header is Authorization: Bearer bgl_… and the key is active in API keys. |
| 404 | The path doesn't exist, or a QR image format isn't png, svg or pdf. | Check the method and path against this reference. |
| 413 | A QR code logo is larger than 256 KB, or a request body is larger than 1 MB. | Send a smaller file or split the request. |
| 429 | You're sending requests faster than the rate limit. The body is empty. | Wait a few seconds and try again, slowing down. See rate limits. |
| 500 | Something went wrong on our side. | Try again. If it keeps happening, contact us with the traceId from the response. |
Any request
| Code | Status | Meaning | What to do |
|---|---|---|---|
workspace.not_found | 404 | The workspace ID in the path doesn't exist, or your key or account can't use it. | Check the workspace ID in Settings → Workspace. An API key only works for the workspace it was created in. |
access.forbidden | 403 | Your role in the workspace, or the key's scopes, don't allow this. | Give the key the scope listed for the endpoint, or ask an Administrator for a role that allows it. |
apikey.owner_removed | 403 | The person who created this API key is no longer in the workspace. | Create a new key while signed in as a current member. |
plan.feature_required | 403 | The feature isn't included in the organisation's plan. API access itself needs the Business plan. | Upgrade in Usage and plan, or leave out the option that needs it. |
organisation.mfa_required | 403 | The organisation requires two-factor authentication and the key's creator (or you) hasn't turned it on. | Turn on two-factor authentication in Settings, then try again. |
account.email_unverified | 403 | The account that owns the key hasn't confirmed its email address, so it can't create links. | Confirm the email address from the message we sent, or ask for a new one from the sign-in page. |
account.suspended | 403 | The organisation is suspended, usually after a failed payment, so new links and QR codes can't be made. | The owner can settle the payment in Usage and plan. Existing links keep working. |
installation.read_only | 403 | This installation of the platform has no valid licence, so links, QR codes and other settings can't be created or changed. Reading still works. | Existing short links keep redirecting. Ask the platform's administrator to install a licence key in Admin › Licence. |
csrf.header_missing | 403 | A signed-in browser request changed something without the X-BGL-Request: 1 header. | Only affects calls made with a browser session. Calls with an API key don't need it. |
Links
| Code | Status | Meaning | What to do |
|---|---|---|---|
link.not_found | 404 | No link with that ID in this workspace (or it was deleted). | Check the link ID. Deleted links can't be fetched. |
link.version_conflict | 409 | expectedVersion doesn't match: someone changed the link since you read it. | Fetch the link again, reapply your change and send the new version. |
link.blocked | 403 | The link was disabled after an abuse report, so it can't be changed. | Contact support if you think this is a mistake. |
link.invalid_transition | 409 | The link can't move from its current status to the one you asked for. | See the allowed status changes on the links page. An archived link goes back to Paused first. |
link.schedule_invalid | 422 | The stop time isn't after the start time. | Send an endsAt later than startsAt. |
link.password_too_short | 422 | Link passwords need at least 4 characters. | Use a longer password. |
link.redirect_type_invalid | 422 | redirectType isn't one of the allowed values. | Use Found (302), MovedPermanently (301) or TemporaryRedirect (307). |
alias.required | 422 | A custom back-half was expected but was empty. | Send a back-half, or leave alias out to get a random one. |
alias.too_short | 422 | The back-half is shorter than the minimum (usually 3 characters). | Use a longer back-half. |
alias.too_long | 422 | The back-half is longer than the maximum (usually 64 characters). | Use a shorter back-half. |
alias.invalid_characters | 422 | The back-half has characters other than letters, numbers, hyphens and underscores. | Remove spaces, slashes, dots and other symbols. |
alias.invalid_edges | 422 | The back-half starts or ends with a hyphen or underscore. | Move or remove the hyphen or underscore. |
alias.reserved | 409 | The back-half is a word this service uses itself, such as admin or docs. | Choose another back-half. |
alias.prohibited | 422 | The back-half contains a word that isn't allowed. | Choose another back-half. |
alias.taken | 409 | Another link on that domain already uses the back-half, including archived and deleted links. | Choose another back-half. Check first with GET /aliases/availability. |
alias.generation_failed | 409 | We couldn't generate a unique random back-half. | Try the request again. |
destination.required | 422 | destinationUrl is missing or empty. | Send the full address the link should go to. |
destination.too_long | 422 | The destination is longer than 4,096 characters. | Shorten the address, for example by removing tracking parameters. |
destination.invalid | 422 | The destination isn't a valid web address. | Send a full address such as https://example.com/page. |
destination.scheme | 422 | Only http and https addresses can be shortened. | Use an http:// or https:// address. |
destination.credentials | 422 | The address contains a username or password (user:pass@host). | Remove the credentials from the address. |
destination.private | 422 | The address points to a private network, localhost or an internal name. | Links must point to a public website. |
destination.loop | 422 | The address is itself a short link on this service, which would loop. | Use the final destination instead. |
link.destination_unsafe | 422 | A threat-intelligence service lists the destination as phishing or malware, so it can't be used. provider and threat say which. | Check the address is the one you meant. If you think the listing is wrong, contact us; reporting it to the listing service also helps. |
link.max_clicks_invalid | 422 | maxClicks isn't a whole number from 1 to 1,000,000,000. | Send a positive whole number, or clearMaxClicks to remove the limit. |
routing.too_many_rules | 422 | routing has more than 20 rules. | Combine rules: one rule can list several countries, devices and languages. |
routing.rule_invalid | 422 | A rule in routing.rules is null. | Send each rule as an object. |
routing.rule_condition_required | 422 | A rule has no countries, devices or languages, so it would catch everyone. | Give the rule at least one condition, or use the link's own destination for everyone. |
routing.too_many_countries | 422 | A rule lists more than 60 countries. | Split the countries across two rules with the same destination. |
routing.country_invalid | 422 | A country isn't a two-letter ISO 3166 code. | Use codes such as AU, NZ or GB. |
routing.device_invalid | 422 | A device isn't one of ios, android, windows, macos or other. | Use one of those values. |
routing.too_many_languages | 422 | A rule lists more than 20 languages. | Split the languages across two rules. |
routing.language_invalid | 422 | A language isn't a two- or three-letter code. | Use the primary language code only, such as en, fr or zh (not en-AU). |
routing.split_size | 422 | routing.split has one destination, or more than five. | Split traffic between 2 and 5 destinations, or send an empty split. |
routing.variant_invalid | 422 | A destination in routing.split is null. | Send each destination as an object. |
routing.weight_invalid | 422 | A split weight isn't a whole number from 1 to 1,000. | Use weights such as 50 and 50, or 80 and 20. |
routing.name_too_long | 422 | A rule or split name is longer than 100 characters. | Use a shorter name. |
routing.id_invalid | 422 | A rule or split id isn't up to 12 lower-case letters and digits. | Send back the id you were given, or leave it out to get a new one. |
routing.id_duplicate | 422 | Two rules or split destinations have the same id. | Give each its own id, or leave ids out. |
routing.app_url_invalid | 422 | An app link isn't an app address (myapp://…), an Android intent: link or an https address. | Use the link your app registers, such as myapp://product/42. javascript:, data: and file: links aren't allowed. |
tags.too_many | 422 | More than 20 tags on one link. | Use 20 tags or fewer. |
tags.too_long | 422 | A tag is longer than 50 characters. | Shorten the tag. |
idempotency.key_too_long | 422 | The Idempotency-Key is longer than 200 characters. | Use a shorter key, such as a UUID. |
idempotency.key_reused | 409 | The Idempotency-Key was already used for a different request in the last 48 hours. | Use a new key for each different link. Reuse a key only to retry exactly the same request. |
idempotency.target_deleted | 409 | The link created by this Idempotency-Key has since been deleted. | Use a new key to create the link again. |
quota.links_exhausted | 429 | The organisation has used this month's allowance of new links. The response has limit and resetsAt fields and a Retry-After header. | Wait until resetsAt, or upgrade. Existing links keep working and edits are never counted. |
platform.no_default_domain | 409 | The service has no default short domain set up yet. | Contact support. |
bulk.empty | 422 | The bulk request had no links in it. | Send at least one link in links. |
bulk.too_many | 429 | The bulk request has more links than your plan allows in one go (1,000 on Business). | Split the links into smaller requests. |
Folders, tags, saved views and bulk changes
| Code | Status | Meaning | What to do |
|---|---|---|---|
folder.not_found | 404 | No folder with that ID in this workspace. | Check the folder ID with GET /folders. |
folder.name_required | 422 | The folder has no name. | Send a name. |
folder.name_too_long | 422 | The name is longer than 100 characters. | Use a shorter name. |
folder.name_taken | 409 | Another folder in the same place already has that name (case doesn't matter). | Choose another name, or put the folder somewhere else. |
folder.too_deep | 409 | The folder would be more than 3 levels deep, or would leave one of its folders more than 3 levels deep. | Put it higher up, or move its folders up first. |
folder.into_itself | 409 | A folder can't move into itself or one of its own folders. | Choose a folder outside it, or moveToTop. |
folder.too_many | 409 | The workspace already has 500 folders. | Delete folders you no longer use. |
tag.not_found | 404 | No tag with that name in this workspace. | Check the name with GET /tags. URL-encode it in the path. |
tag.name_required | 422 | The new name is empty. | Send the new name in name. |
view.not_found | 404 | No saved view with that ID that you can see. | Check the ID with GET /saved-views. Other people's private views aren't visible. |
view.not_yours | 403 | Someone else saved this view, so only they can change it. | Save your own copy instead. |
view.name_required | 422 | The view has no name. | Send a name. |
view.name_too_long | 422 | The name is longer than 100 characters. | Use a shorter name. |
view.share_forbidden | 403 | Only people who can edit links can share views with the workspace. | Save it without shared, or ask a Manager to share one. |
view.too_many | 409 | You already have 50 saved views in this workspace. | Delete views you no longer use. |
utm_template.not_found | 404 | No UTM template with that ID in this workspace. | Check the ID with GET /utm-templates. |
utm_template.name_required | 422 | The template has no name. | Send a name. |
utm_template.name_too_long | 422 | The name is longer than 100 characters. | Use a shorter name. |
utm_template.name_taken | 409 | Another template in the workspace already has that name (case doesn't matter). | Choose another name, or change the existing template. |
utm_template.empty | 422 | Every UTM tag is empty. | Fill in at least one of utmSource, utmMedium, utmCampaign, utmTerm or utmContent. |
utm_template.value_too_long | 422 | A UTM tag is longer than 200 characters. | Shorten it. |
utm_template.too_many | 409 | The workspace already has 100 UTM templates. | Delete templates you no longer use. |
bulk.action_invalid | 422 | action isn't one of move, addTags, removeTags, pause, resume, setExpiry, clearExpiry or delete. | Check the spelling; it's case-insensitive. |
bulk.selection_invalid | 422 | The request sent both linkIds and filter, or neither, or an empty linkIds. | Send exactly one of them. |
bulk.too_many_ids | 422 | linkIds has more than 1,000 IDs. | Send them in several requests, or use filter. |
bulk.too_many_matches | 409 | The filter matches more than 10,000 links. | Narrow the filter, for example by createdFrom and createdTo, and send several requests. |
bulk.count_changed | 409 | The filter now matches a different number of links than expectedCount, so nothing was changed. count says how many it matches now. | Check the list again, then send the new count. |
bulk.tags_required | 422 | addTags or removeTags was sent without any tags. | List the tags in tags. |
bulk.ends_at_required | 422 | setExpiry was sent without endsAt. | Send the time the links should stop working. |
QR codes
| Code | Status | Meaning | What to do |
|---|---|---|---|
qr.not_found | 404 | No QR code with that ID in this workspace. | Check the QR code ID. |
quota.qr_exhausted | 429 | The organisation has used this month's allowance of new QR codes. The response has limit and resetsAt fields. | Wait until resetsAt, or upgrade. Existing QR codes keep working. |
qr.colour_invalid | 422 | A colour isn't a six-digit hex value. | Send colours like #15181C. |
qr.inverted | 422 | The foreground is lighter than the background. Many scanners can't read that. | Use a dark foreground on a light background. |
qr.low_contrast | 422 | The colours are too close together to scan reliably (contrast below 4:1). | Use a darker foreground or a lighter background. |
qr.quiet_zone_invalid | 422 | The margin is outside 2 to 10 modules. | Use a quietZone between 2 and 10. 4 is standard. |
qr.error_correction_invalid | 422 | errorCorrection isn't one of the allowed values. | Use Low, Medium, Quartile or High. |
qr.logo_needs_high | 422 | Codes with a logo must use High error correction. | Remove the logo first, or keep High. |
qr.logo_size | 422 | The logo is empty or larger than 256 KB. | Send a PNG of 256 KB or less. |
qr.logo_invalid | 422 | The logo isn't a PNG we can read. | Export the logo again as a standard PNG. |
qr.logo_small | 422 | The logo is smaller than 16 × 16 pixels. | Use a larger image; square with a transparent background works best. |
qr.id_generation_failed | 409 | We couldn't generate a unique QR code ID. | Try the request again. |
Analytics and chart notes
| Code | Status | Meaning | What to do |
|---|---|---|---|
analytics.time_zone_invalid | 422 | timeZone isn't a time zone we know. | Use an IANA name such as Australia/Sydney or America/New_York, or UTC. |
analytics.time_zone_history | 422 | A report in a zone other than the workspace's is counted from raw clicks, and the period starts before the oldest raw clicks kept. | Start the period later, as the message says, or leave out timeZone to use the workspace's zone. |
note.not_found | 404 | No note with that ID in this workspace. | Check the ID with GET /notes. |
note.text_required | 422 | text is missing or blank. | Write the note, such as "Newsletter sent". |
note.text_too_long | 422 | text is longer than 200 characters. | Shorten the note. |
note.date_required | 422 | date is missing. | Send the date as YYYY-MM-DD, in the workspace's time zone. |
note.date_invalid | 422 | date is before 2000 or after 2100. | Check the year. |
note.time_invalid | 422 | time isn't a 24-hour time. | Send HH:MM, such as 09:30, or leave time out. |
note.target_conflict | 422 | Both linkId and campaignId were sent. | A note is for one link, one campaign or the whole workspace: send at most one of them. |
Link health
| Code | Status | Meaning | What to do |
|---|---|---|---|
link_health.not_checkable | 409 | The link isn't live (it's paused, scheduled, expired, archived or disabled), so it isn't checked. | Make the link live first, or leave it: it's checked once it's live. |
link_health.too_soon | 429 | The link was checked by hand less than a minute ago, or the workspace has checked 30 links by hand in the last hour. | Wait and try again. Links are also checked automatically. |
link_health.off | 409 | Link checks are switched off on this service. | Ask the service's administrator. |
Conversions
| Code | Status | Meaning | What to do |
|---|---|---|---|
conversion.click_invalid | 422 | The click value isn't a bgl_click ID we issued, or it was changed. | Send the bgl_click value exactly as it arrived on your page, without decoding or trimming it. |
conversion.click_not_found | 404 | The click ID is for a link in another workspace. | Use an API key for the workspace that owns the link. |
conversion.event_required | 422 | event is missing. | Name the event, such as signup or purchase. |
conversion.event_too_long | 422 | event is longer than 64 characters. | Use a shorter event name. |
conversion.event_invalid | 422 | event contains control characters such as line breaks. | Use plain text. |
conversion.event_id_too_long | 422 | eventId is longer than 200 characters. | Use a shorter ID, such as your order number. |
conversion.value_required | 422 | currency was sent without value. | Send both, or neither. |
conversion.currency_required | 422 | value was sent without currency. | Add a three-letter currency code, such as AUD. |
conversion.currency_invalid | 422 | currency isn't a three-letter code. | Use an ISO 4217 code, such as AUD, NZD or USD. |
conversion.value_too_large | 422 | value is too large to store. | Check the amount; send it in whole currency units, such as 49.95, not cents. |
conversion.in_future | 422 | occurredAt is in the future. | Leave occurredAt out to use the current time, or check your server's clock. |
conversion.before_click | 422 | occurredAt is before the click happened. | Send the time the conversion happened, or leave it out. |
conversion.outside_window | 422 | The conversion happened more than 90 days after the click, so it no longer counts towards the link. | Nothing to fix; conversions are only counted within 90 days of the click. |
Webhooks
| Code | Status | Meaning | What to do |
|---|---|---|---|
webhook.not_found | 404 | No webhook with that ID in this workspace. | Check the webhook ID with GET /webhooks. |
webhook.delivery_not_found | 404 | No delivery with that ID for this webhook. | Check the delivery ID with GET /webhooks/{webhookId}/deliveries. Deliveries are kept for 30 days. |
webhook.url_required | 422 | url is missing. | Send the full https:// address to post events to. |
webhook.url_invalid | 422 | url isn't a full web address, or it includes a user name or password. | Send an address such as https://example.com/hooks/blackgum. |
webhook.url_https | 422 | url starts with http://. | Use https://, so events are encrypted on the way. |
webhook.url_private | 422 | url points at a local or private network address. | Use an address on the public internet. To test on your own computer, use a tunnelling tool that gives you a public https address. |
webhook.url_not_slack | 422 | format is slack, but url isn't a Slack incoming webhook address. | Copy the address from your Slack app's Incoming Webhooks page. It starts with https://hooks.slack.com/. |
webhook.url_not_teams | 422 | format is teams, but url isn't a Microsoft Teams webhook address. | Copy the address from the Teams workflow "Post to a channel when a webhook request is received". |
webhook.description_too_long | 422 | description is longer than 200 characters. | Use a shorter description. |
webhook.format_invalid | 422 | format isn't one of the allowed values. | Use Json, Slack or Teams. |
webhook.events_required | 422 | events is empty. | Choose at least one event type. |
webhook.event_unknown | 422 | events contains a name we don't send. | Use the event names listed in the webhooks guide, such as link.created. |
webhook.threshold_invalid | 422 | A click milestone isn't a whole number from 1 to 1,000,000,000. | Send numbers such as 100 and 1000. |
webhook.too_many_thresholds | 422 | More than 10 click milestones. | Keep the ones that matter most. |
webhook.thresholds_required | 422 | link.click_threshold is chosen but there are no click milestones. | Add clickThresholds, such as [100, 1000], or leave out link.click_threshold. |
webhook.limit_reached | 409 | The workspace already has 10 webhooks. | Remove one you no longer use. One webhook can receive several event types. |
webhook.disabled | 409 | The webhook is turned off, so it can't be sent a test or a repeat. | Turn it on with PATCH (enabled: true) first. |
Campaigns
| Code | Status | Meaning | What to do |
|---|---|---|---|
campaign.not_found | 404 | No campaign with that ID in this workspace. When you add a link to a campaign, an archived one counts as missing. | Check the campaign ID with GET /campaigns. |
campaign.name_required | 422 | The campaign has no name. | Send a name. |
campaign.name_too_long | 422 | The name is longer than 200 characters. | Use a shorter name. |
campaign.utm_too_long | 422 | A UTM value is longer than 200 characters. | Use shorter UTM values. |
Custom domains
| Code | Status | Meaning | What to do |
|---|---|---|---|
domain.not_found | 404 | No domain with that ID in this workspace, or it isn't available to it. | Check the domain ID with GET /domains. |
domain.invalid | 422 | The hostname isn't a valid domain or subdomain. | Send a hostname such as go.example.com, without https:// or a path. |
domain.reserved | 409 | The hostname belongs to this service. | Use a domain you own. |
domain.taken | 409 | Another workspace has already added this hostname. | If you own it, contact support. |
domain.limit_reached | 429 | The organisation has as many custom domains as its plan allows. | Remove a domain you no longer use, or upgrade. |
domain.not_ready | 409 | The domain isn't verified and active yet, so links can't use it. | Finish the DNS set-up and check the domain until it shows Active. |
domain.in_use | 409 | Links still use the domain, so it can't be removed. | Delete or archive those links first. |
Something missing or unclear? Tell us and we'll improve this page.