Support Tickets API

Open, follow and answer support tickets from a script, a CI job or an AI agent. The API does what the console does: the same fields, the same rules, the same emails.

Before you start

  • The token needs an ability. support:read lists and reads tickets. support:write opens tickets, replies to them and marks them resolved. New tokens do not get either by default, and tokens you created earlier do not have them. Open API Tokens, choose Permissions on the token and add them, or create a new token. A token with all abilities, such as the one danube login creates, can use tickets too.
  • Your email address must be verified. Until it is, every call answers 403.
  • Tickets belong to a project. A call acts on the project its token is bound to. With an account-wide token, send the project's id in the X-Team-Id header. See Project scope.
  • Support is always reachable. Tickets work when your project has no payment method yet, when a payment failed, and when it is suspended for an unpaid invoice, so you can always tell us. A project we blocked or terminated gets 403 on every call, and a project we suspended can read tickets but not write them. In those cases, email support@danubedata.ro.

Tickets and replies sent with a token are marked

A token proves that someone holds a credential, not who is asking. Tokens live in CI systems and agents, and they leak. So we treat what a token sends like a request that came by email:

  • A ticket you open with a token is marked as opened with an API token, with the token's name as it was when you opened it. Our team reads that name as plain text on a line of its own: it is shortened to 40 characters, and quotation marks, line breaks and other control characters are removed.
  • A reply you send with a token is marked the same way, even on a ticket you opened in the console. Our team sees which replies came from a token and which from a person signed in.
  • Before we change anything or share account data because of what a token sent, we verify the sender with your customer number, account email and support PIN. Set a support PIN beforehand, or we cannot act on it.
  • We tell you when a token speaks. The confirmation email for a ticket opened with a token says so and names the token. It does not repeat the subject or the message: read those in the console. A reply sent with a token sends you a short email that names the ticket and the token, and nothing of what was written, at most once an hour for each ticket. That is how you find out about a token you did not expect to be in use. If it is not yours, delete it under API Tokens and email support@danubedata.ro.
  • Tickets and replies you send from the console are not affected: there we already know it is you.

Give a token support:write only when it needs to open or answer tickets. A token with support:read alone can follow a ticket without being able to start one.

Who can see a ticket

A call reaches the tickets its user opened in the current project, and no one else's. Another member's ticket, or one of yours in another project, answers 404, whatever your role. Internal notes that our team writes are never returned. Files are attached and read in the console only: the API neither takes nor lists them.

Endpoints

The {number} in a URL is the ticket number you see in the console and in emails, such as TICK-2026-00224.

MethodEndpointDescriptionAbility
GET/api/v1/support-ticketsList your ticketssupport:read
POST/api/v1/support-ticketsOpen a ticketsupport:write
GET/api/v1/support-tickets/{number}Show a ticket with its repliessupport:read
POST/api/v1/support-tickets/{number}/repliesReply to a ticketsupport:write
POST/api/v1/support-tickets/{number}/resolveMark a ticket resolvedsupport:write

Open a ticket

Bash
curl -X POST 'https://danubedata.ro/api/v1/support-tickets' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "type": "technical",
    "priority": "high",
    "subject": "Deploys fail with an image pull error",
    "message": "Every deploy since this morning fails with ErrImagePull, on all three of our services.",
    "resource_type": "serverless",
    "resource_id": "9c8b7a6e-5d4c-3b2a-1098-76543210fedc"
  }'
FieldRequiredValues
typeYesgeneral, technical, billing or sales
priorityYeslow, normal, high or urgent
subjectYesUp to 255 characters
messageYes10 to 10,000 characters
resource_typeNocache, database, vps, storage, queue, serverless, static_site or app
resource_idWith resource_typeThe resource's UUID

Send JSON. A request that names attachments is refused with 422: add files in the console.

A resource that is not in the project is left off, as it is in the console, and the ticket is still opened. Check resource in the response to see what it was linked to.

Text is kept exactly as you send it. Nothing is stripped, and every page and email that shows it escapes it. The one exception is the email we send you about a ticket a token opened, which leaves the subject and the message out.

Response (201 Created)

JSON
{
  "message": "Support ticket created",
  "ticket": {
    "number": "TICK-2026-00224",
    "subject": "Deploys fail with an image pull error",
    "type": "technical",
    "priority": "high",
    "status": "open",
    "resource": {
      "type": "serverless",
      "id": "9c8b7a6e-5d4c-3b2a-1098-76543210fedc",
      "name": "api"
    },
    "created_at": "2026-09-29T08:12:45+00:00",
    "updated_at": "2026-09-29T08:12:45+00:00",
    "message": "Every deploy since this morning fails with ErrImagePull, on all three of our services.",
    "opened_by_support": false,
    "replies": []
  }
}

resource is null for a ticket about nothing in particular. Its name is null once the resource has been deleted. On a ticket our team opened about your account limits, type is account_limit, id is null and name is Your account limits. The list and a single ticket show resource the same way.

List your tickets

Bash
curl 'https://danubedata.ro/api/v1/support-tickets?status=waiting_customer' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Accept: application/json'

Newest first, 15 to a page.

QueryValues
statusopen, in_progress, waiting_customer, resolved or closed. Anything else is refused with 422.
per_page1 to 100. Default 15.
pageThe page to read. Default 1.

waiting_customer is a ticket that support has answered and now waits on you for.

JSON
{
  "data": [
    {
      "number": "TICK-2026-00224",
      "subject": "Deploys fail with an image pull error",
      "type": "technical",
      "priority": "high",
      "status": "waiting_customer",
      "resource": null,
      "created_at": "2026-09-29T08:12:45+00:00",
      "updated_at": "2026-09-29T09:40:02+00:00"
    }
  ],
  "pagination": {
    "current_page": 1,
    "last_page": 1,
    "per_page": 15,
    "total": 1
  }
}

Show a ticket

Bash
curl 'https://danubedata.ro/api/v1/support-tickets/TICK-2026-00224' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Accept: application/json'

You get the ticket as data shows it, plus its first message and every reply, oldest first.

JSON
{
  "ticket": {
    "number": "TICK-2026-00224",
    "subject": "Deploys fail with an image pull error",
    "type": "technical",
    "priority": "high",
    "status": "waiting_customer",
    "resource": null,
    "created_at": "2026-09-29T08:12:45+00:00",
    "updated_at": "2026-09-29T09:40:02+00:00",
    "message": "Every deploy since this morning fails with ErrImagePull, on all three of our services.",
    "opened_by_support": false,
    "replies": [
      {
        "id": 8121,
        "message": "Which registry do the three services pull from?",
        "from_support": true,
        "author": "Adrian",
        "created_at": "2026-09-29T09:40:02+00:00"
      }
    ]
  }
}
  • from_support says which side wrote a reply. author is the agent's name, or DanubeData support when none is named, as the console shows it.
  • opened_by_support is true for a ticket our team opened for you, such as the answer to a limit increase request. Its first message is ours.
  • Reading a ticket over the API does not mark our replies as read in the console, so the person who uses the console still sees what is new.

Reply to a ticket

Bash
curl -X POST 'https://danubedata.ro/api/v1/support-tickets/TICK-2026-00224/replies' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{"message": "All three pull from cr.danubedata.ro."}'

message is 1 to 10,000 characters. A reply is always public: it is yours, and never an internal note.

A ticket that was waiting on you, or that was resolved or closed, is open again, and we are told by email. The email and the ticket page in our admin say that the reply came from an API token, and name it. You get a short email about it too, at most once an hour for each ticket: see above.

Response (201 Created)

JSON
{
  "message": "Reply sent",
  "reply": {
    "id": 8122,
    "message": "All three pull from cr.danubedata.ro.",
    "from_support": false,
    "author": "Ana Pop",
    "created_at": "2026-09-29T09:52:10+00:00"
  },
  "ticket": {
    "number": "TICK-2026-00224",
    "subject": "Deploys fail with an image pull error",
    "type": "technical",
    "priority": "high",
    "status": "open",
    "resource": null,
    "created_at": "2026-09-29T08:12:45+00:00",
    "updated_at": "2026-09-29T09:52:10+00:00"
  }
}

Mark a ticket resolved

Bash
curl -X POST 'https://danubedata.ro/api/v1/support-tickets/TICK-2026-00224/resolve' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Accept: application/json'

The response is {"message": "Ticket marked as resolved", "ticket": {...}}. A resolved ticket is open again when you reply to it. A ticket that is already resolved or closed answers 409.

Retry safely

If the connection drops before you read the answer, you cannot tell whether the ticket was opened or the reply sent. Send an Idempotency-Key header with POST /api/v1/support-tickets and with POST /api/v1/support-tickets/{number}/replies, and retry with the same key and the same body. You get the first answer back, marked Idempotent-Replay: true, and nothing is opened or sent twice, so our team is not emailed again.

Bash
curl -X POST 'https://danubedata.ro/api/v1/support-tickets/TICK-2026-00224/replies' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'Idempotency-Key: 6f1c1b0e-0f3b-4f57-9d0a-3f0c9e0a5a11' \
  -d '{"message": "All three pull from cr.danubedata.ro."}'
  • Use a new key for every request you mean to be different, such as a UUID. A key is up to 255 characters.
  • A key is yours alone. Another member of your project who sends the same key makes a request of their own, and does not get your ticket back.
  • A key is remembered for the address it was used on (a reply to another ticket is another request), and for at least a day. Never reuse a key for a different request, even much later: it may still be remembered.
  • The same key with a different body answers 409 with error.code idempotency.key_reused. While the first request is still running, the answer is 409 with idempotency.in_progress and "retryable": true: try again in a moment.
  • Only a successful answer is kept. A request that failed, with 422 for instance, does not use up its key: correct it and send it again with the same key.
  • A replay counts towards the hourly limits below, because they are counted before we read the key.
JSON
{
  "success": false,
  "data": null,
  "error": {
    "code": "idempotency.key_reused",
    "message": "This Idempotency-Key was already used with a different request body. Use a new key for a new request.",
    "retryable": false
  },
  "meta": {}
}

Limits

Each person can open 10 tickets and send 60 replies an hour, counted across all of their tokens and projects: a second token does not add to it. A request that a token is refused for, because it lacks support:write, is not counted. Reading is not held to these two limits, but the API rate limits of your account apply to every call.

Past a limit the API answers 429, with the seconds to wait in retry_after and in the Retry-After header:

JSON
{
  "message": "You can open 10 tickets an hour with the API. Try again in 42 minutes, or email support@danubedata.ro.",
  "retry_after": 2520
}

Wait for our answer

Our replies reach you by email as usual. To follow them from a script, ask for the tickets that wait on you and read each one:

Bash
curl 'https://danubedata.ro/api/v1/support-tickets?status=waiting_customer' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Accept: application/json'

A few minutes between calls is plenty: a person answers a ticket, and a loop that asks every second only spends your rate limit.

Errors

StatusWhen
400The Idempotency-Key is longer than 255 characters.
401The token is missing, wrong or revoked.
403The token lacks support:read or support:write, your email address is not verified, the token is bound to another project than the one you named, or the project is blocked, terminated or suspended.
404No such ticket among yours in the current project.
409Resolving a ticket that is already resolved or closed, or an Idempotency-Key that was used with another body or is still running.
422A field failed validation, or the request names attachments. The body lists each field in errors.
429You reached the limit for new tickets or replies.

A validation error looks like this:

JSON
{
  "message": "Validation failed.",
  "errors": {
    "type": ["The type must be general, technical, billing or sales."]
  }
}

Next steps