Create your first project, API key, ticket, custom field, and webhook.
These examples use the current OpenAPI contract and `/api/v1` paths. Tenant context comes from the bearer credential you send in `Authorization`, either a tenant-scoped JWT or an `rgk_` API key. Do not send `X-Tenant-Id`; tenant headers are rejected.
Set variables once.
Use a tenant-scoped JWT for account administration calls. Use the API key returned later for app and automation calls.
export RG_API_BASE="https://app.rustgrid.com"
export JWT_TOKEN="paste_tenant_scoped_jwt_here"
export IDEMPOTENCY_KEY="$(uuidgen)"
Create your first project.
A project key routes tickets without exposing tenant IDs in client requests.
curl -sS -X POST "$RG_API_BASE/api/v1/projects" \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: project-$IDEMPOTENCY_KEY" \
-d '{
"key": "DEMO",
"name": "Demo Project",
"description": "Quickstart project for API examples"
}'
Save the response id as PROJECT_ID for project-scoped endpoints.
export PROJECT_ID="paste_project_id_from_response_here"
Generate an API key.
The full rgk_ key is returned only once. Store it in your secret manager before leaving the response.
curl -sS -X POST "$RG_API_BASE/api/v1/api-keys" \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Quickstart automation",
"project_id": "'$PROJECT_ID'",
"scopes": [
"projects:read",
"tickets:create",
"tickets:read",
"tickets:list",
"custom_fields:create",
"custom_fields:read",
"webhooks:create",
"webhooks:read"
]
}'
export RG_API_KEY="paste_rgk_key_from_response_here"
Create your first ticket.
Send project_key or project_id. The quickstart uses project_key so the command remains readable.
curl -sS -X POST "$RG_API_BASE/api/v1/tickets" \
-H "Authorization: Bearer $RG_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ticket-$IDEMPOTENCY_KEY" \
-d '{
"project_key": "DEMO",
"title": "First RustGrid ticket",
"description": "Created through the RustGrid quickstart.",
"type": "task",
"priority": "medium",
"status": "todo"
}'
List tickets.
Ticket lists are paginated. The response includes items, page, size, and total.
curl -sS "$RG_API_BASE/api/v1/tickets?page=1&size=20" \
-H "Authorization: Bearer $RG_API_KEY"
Add custom fields.
Custom fields are project-level definitions. Supported field types include text, number, date, boolean, single_select, and multi_select.
curl -sS -X POST "$RG_API_BASE/api/v1/projects/$PROJECT_ID/custom-fields" \
-H "Authorization: Bearer $RG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "customer_tier",
"name": "Customer Tier",
"description": "Priority segment for support workflows",
"field_type": "single_select",
"is_required": false,
"position": 10,
"options": [
{ "value": "standard", "label": "Standard", "color": "#0F6FFF" },
{ "value": "priority", "label": "Priority", "color": "#168A5B" }
]
}'
curl -sS "$RG_API_BASE/api/v1/projects/$PROJECT_ID/custom-fields?page=1&size=20" \
-H "Authorization: Bearer $RG_API_KEY"
Create a webhook subscription.
The create response may include a signing secret. Store it once, then use it to verify deliveries.
curl -sS -X POST "$RG_API_BASE/api/v1/webhooks" \
-H "Authorization: Bearer $RG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Quickstart webhook",
"url": "https://example.com/rustgrid/webhook",
"project_id": "'$PROJECT_ID'",
"event_types": ["ticket.created", "ticket.updated"]
}'
Verify webhook signatures.
Verify against the exact raw request body bytes your server received. Do not reserialize parsed JSON before computing the HMAC.
node - <<'NODE'
const crypto = require('crypto');
function verifyRustGridWebhook(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const received = String(signatureHeader || '').replace(/^sha256=/, '');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
const ok = verifyRustGridWebhook(
Buffer.from(process.env.RUSTGRID_WEBHOOK_BODY || '', 'utf8'),
process.env.RUSTGRID_WEBHOOK_SIGNATURE,
process.env.RUSTGRID_WEBHOOK_SECRET
);
process.stdout.write(ok ? 'valid\n' : 'invalid\n');
NODE
Use the signature header name emitted by your RustGrid deployment. Keep webhook secrets out of logs.
Common errors and request IDs.
Every response includes or propagates x-request-id. Include that value when debugging failed calls.
| Status | Typical cause | Action |
|---|---|---|
| 400 | Payload shape or enum value does not match the OpenAPI schema. | Check required fields, priority, type, status, and custom field types. |
| 401 | Missing, expired, or invalid bearer credential. | Send Authorization: Bearer ...; do not send X-Tenant-Id. |
| 403 | The credential lacks the needed scope or RBAC permission. | Create a key with the scopes used by the endpoint. |
| 404 | The resource is absent or outside the caller tenant. | Confirm IDs came from the same bearer credential context. |
| 409 | Duplicate resource or idempotency conflict. | Reuse the same body for idempotency replay or send a new key. |
| 429 | Route, tenant, or plan rate limit reached. | Back off using retry-after and inspect x-ratelimit-* headers. |
curl -sS -D /tmp/rustgrid-headers.txt "$RG_API_BASE/api/v1/tickets?page=1&size=1" \
-H "Authorization: Bearer $RG_API_KEY" \
-o /tmp/rustgrid-body.json
printf "status/request headers:\n"
grep -iE '^(http/|x-request-id:|retry-after:|x-ratelimit-)' /tmp/rustgrid-headers.txt
cat /tmp/rustgrid-body.json