File issues via the public API
If your own application already knows something is broken — a crash your mobile app caught, a failed job your backend detected, a bug report a customer typed into your product’s own feedback box — you don’t have to route it through a conversation first. Four endpoints under /api/v1/issues let you create, list, read, and update issues in your Atender tracker directly, authenticated with the same tenant API key you use for the rest of the v1 API.
What you’ll need
- A tenant API key, generated from Settings → API Keys.
- The
issues:writescope to create or update issues. - The
issues:readscope to list or fetch issues.
A key can carry both scopes if your integration needs to do both. Scopes are checked per endpoint — a key with only issues:read can list and fetch, but a create or patch call against it is rejected.
Create an issue
POST /api/v1/issues
Requires issues:write.
{
"title": "Checkout button does nothing on Safari",
"body": "Tapping Pay on iOS Safari 17 does not submit the form.",
"type": "bug",
"module": "billing",
"customerPriority": "urgent",
"reporterEmail": "jane@customer.com",
"reporterName": "Jane Doe",
"conversationId": "conv_9f2a",
"appVersion": "4.12.0",
"pageUrl": "https://app.customer.com/checkout",
"pageTitle": "Checkout"
}
A successful create returns the new issue, including the tenant-scoped issue number and the fields Atender filled in for you:
{
"id": "iss_01hz...",
"number": 214,
"tenantId": "your-tenant",
"environment": "production",
"status": "new",
"type": "bug",
"module": "billing",
"priority": "medium",
"customerPriority": "urgent",
"reporterContactId": "contact_88a1",
"title": "Checkout button does nothing on Safari",
"body": "Tapping Pay on iOS Safari 17 does not submit the form.",
"createdAt": "2026-08-29T10:00:00.000Z",
"updatedAt": "2026-08-29T10:00:00.000Z"
}
type, module, priority, and status are drawn from the same fixed vocabularies Atender’s own reporter form uses — send a value outside those lists and the request is rejected.
List issues
GET /api/v1/issues?state=open&status=new&reporterContactId=contact_88a1&page=1&perPage=30
Requires issues:read.
Filters are all optional: state (open or closed), status, and reporterContactId narrow the result set; page and perPage paginate it. The response shape is consistent regardless of which filters you pass:
{
"issues": [ /* array of issue objects, same shape as create returns */ ],
"page": 1,
"perPage": 30,
"total": 214,
"hasMore": true
}
Every row returned belongs to your tenant only — there’s no way to pass a tenantId filter to see anyone else’s data, because the tenant scoping isn’t a filter, it’s the boundary of the query.
Read a single issue
GET /api/v1/issues/:number
Requires issues:read. :number is the tenant-scoped issue number returned by create and list — not a global database id. Asking for a number that doesn’t exist in your tenant (including one that exists in someone else’s) returns a not-found response, not another tenant’s issue.
Update an issue
PATCH /api/v1/issues/:number
Requires issues:write. Send only the fields you want to change:
{
"status": "in-progress",
"customerPriority": "high"
}
The response is the updated issue in the same shape as create and read.
How the reporter is identified
Every issue needs a reporter, and the API gives you two ways to say who that is:
reporterContactId— pass the id of a contact that already exists in your tenant. It’s verified against your tenant before the issue is created; a contact id from another tenant, or one that doesn’t exist, is rejected rather than silently attached.reporterEmail(optionally withreporterName) — Atender resolves this to an existing contact by address, or creates one if none exists yet, then attaches that contact as the reporter.
Use reporterContactId when your application already tracks the Atender contact id for the user (for example, you created that contact through the API earlier). Use reporterEmail/reporterName when your application only knows the customer’s email address and wants Atender to handle contact matching.
Tenant and environment are always derived from your key
Every issue you create is stamped with the tenant and environment your API key belongs to — never with anything you send in the request body. If your request body includes a tenantId field, the request is refused outright rather than having that field quietly ignored, so you’ll find out immediately if your integration is sending something it shouldn’t.
environment works the same way: it’s read from whether your key is a production or sandbox key, not from a value you choose. This means a report filed by a customer’s staging build, using a sandbox key, lands in the same backlog as production issues and is clearly marked as having come from staging — you don’t need separate tracking to tell the two apart.
customerPriority vs. priority
The API distinguishes two priority fields, and they’re meant to stay separate:
customerPriority— set by the caller. This is the urgency your application (or the customer, through your application) assigns to the report. It’s the same field Atender’s own reporter form uses to record how the reporter feels about the issue.priority— the team’s own priority, set and changed by whoever triages the issue in Atender. A create request that omitsprioritygets a default; a create or patch request cannot usecustomerPriorityto override the team’spriority.
Most integrations only ever set customerPriority, since your application usually knows how urgent the report feels to the person who filed it, but has no reason to know how Atender’s team has triaged its backlog.
Linking a conversation
If the issue you’re filing came out of an existing conversation, pass its id as conversationId on create:
{
"title": "...",
"conversationId": "conv_9f2a"
}
If the conversation id belongs to your tenant, the issue is linked to it. If it doesn’t — a typo, a stale id, an id from another tenant — the link is simply not made. The issue is still created either way; a bad conversationId never blocks or fails the create call. Check the created issue afterward if you need to confirm the link succeeded.
What’s not supported yet
The public API does not currently accept attachment or screenshot paths on create or update. There’s no upload endpoint on this API, so there’s no way for a caller to obtain a valid storage path to submit — and accepting an arbitrary string here would let a caller point an issue at storage it doesn’t own. If your integration needs to attach screenshots to a report, that’s not available through /api/v1/issues today.