Skip to main content

Create a claim

You need the entity_id of a connected business. See Connect a business.

partner_claim_id is your id for the case. Use the same value to upload the documents and to create the claim, and again when you retry.

The examples use the live entity_id aZ4kQ9pX2. With a test key, use sandbox or an entity_id from GET /entities.

Step 1: upload the documents​

This step is optional. Each document needs two requests. Do them before you create the claim.

Get a signed URL​

Required: entity_id, partner_claim_id, filename, mime_type.

LimitValue
File size1 byte to 50 MB, or 5 MB until Garfield approves your partner account. max_bytes can set a lower limit. A higher value fails with 400 validation_failed.
Permitted mime_typePDF, JPEG, PNG, DOCX, plain text and CSV. Convert other formats to one of these first. The API reference lists the exact values.
filename extensionMust match mime_type, for example .pdf for application/pdf. Otherwise the request fails with 400 validation_failed.
curl -X POST "$BASE/documents/upload-url" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entity_id": "aZ4kQ9pX2",
"partner_claim_id": "case-2026-00412",
"filename": "invoice-00412.pdf",
"mime_type": "application/pdf",
"max_bytes": 5242880
}'

201 Created:

{
"upload_url": "https://storage.googleapis.com/...",
"storage_path": "entities/aZ4kQ9pX2/claims/pc_af66fa4b5cb7283356863b7554d4e140/documents/0TCsxImkxYI.pdf",
"method": "PUT",
"expires_at": "2026-05-31T15:19:05.000Z",
"required_headers": {
"Content-Type": "application/pdf",
"x-goog-content-length-range": "1,5242880"
}
}

The upload_url expires after 15 minutes.

Upload the file​

Send the file to upload_url with the required_headers exactly as you got them. This request goes to Google Cloud Storage, so leave out the bearer token. Use PUT. Opening the URL in a browser sends a GET, which returns MalformedSecurityHeader.

curl -X PUT "<upload_url>" \
-H "Content-Type: application/pdf" \
-H "x-goog-content-length-range: 1,5242880" \
--data-binary @invoice-00412.pdf

To add a document to a claim that already exists, upload it the same way with the claim's partner_claim_id, then send its storage_path in a message.

Step 2: create the claim​

Required: entity_id, partner_claim_id, defendant.name, and one or more invoices. All other fields are optional.

Put all context for Garfield in message, up to 10,000 characters: the history of the debt, what the documents are, and your instructions. message and document_refs become the first message of the claim. That message starts Garfield's processing. If you send neither, the first message is a fixed note saying the claim came through the API without a note, so Garfield's processing still starts.

Garfield acts on every message you send as it would on the business's own. It sends nothing, charges nothing and files nothing at court until the business confirms that step in Garfield.

The connected entity is always the claimant. A firm that acts for clients can raise claims through the API only for its own debts.

curl -X POST "$BASE/claims" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @claim.json
claim.json
{
"entity_id": "aZ4kQ9pX2",
"partner_claim_id": "case-2026-00412",
"partner_user_id": "acme-crm-7741",
"partner_invoice_ids": ["crm-inv-88", "crm-inv-91"],
"message": "Two overdue invoices, disputed once. Invoice attached.",
"defendant": {
"name": "Debtor Ltd",
"email": "accounts@debtor.example",
"address": {
"first_line": "1 High Street",
"town_city": "London",
"county": "Greater London",
"postcode": "EC1A 1BB",
"country": "United Kingdom"
},
"is_consumer": false,
"is_individual": false,
"company_registration_number": "12345678"
},
"invoices": [
{
"number": "INV-1001",
"total_amount_for_invoice": "1500.00",
"created_date": "2026-01-10",
"due_date": "2026-02-10",
"summary": "Consulting services"
},
{
"number": "INV-1002",
"total_amount_for_invoice": "900.00",
"created_date": "2026-02-01",
"due_date": "2026-03-03",
"summary": "Consulting services, phase 2",
"line_items": [
{
"description": "Consulting, phase 2",
"quantity": "6",
"unit_price": "125.00",
"tax": "150.00"
}
]
}
],
"contract_date": "2026-01-01T00:00:00.000Z",
"is_written": true,
"document_refs": [
{
"storage_path": "entities/aZ4kQ9pX2/claims/pc_af66fa4b5cb7283356863b7554d4e140/documents/0TCsxImkxYI.pdf",
"mime_type": "application/pdf",
"filename": "invoice-00412.pdf"
}
]
}
FieldUse
invoices[].numberThe number printed on the invoice. Garfield uses it in the claim.
partner_invoice_idsYour own ids for the invoices. Only for your reconciliation. GET /claims returns them.
document_refsThe storage_path, mime_type and filename of each file from step 1, up to 20 files.
partner_user_idMust match the partner_user_id the entity connected with, or the API returns 409 partner_user_id_mismatch.

The defendant address uses town_city and county. The onboarding address uses locality and region. Do not mix the two.

Dates and amounts​

Garfield works out the limitation period and interest from these values, so the API rejects a malformed one with 400 validation_failed.

  • Send invoices[].created_date, invoices[].due_date and contract_date as YYYY-MM-DD, for example 2026-05-31. The API also accepts an ISO-8601 date-time with Z or an offset, but the offset can move the date across midnight, so send the plain date. An empty string means unknown, the same as null. The API rejects anything else, such as 31/05/2026, 2026-5-1, 2026-02-30 or a date-time without an offset.
  • Send invoices[].total_amount_for_invoice as a decimal string with at most two decimal places, for example "4200.00" or "4200". The API rejects currency symbols and commas, as in "£4,200".

The error's details.fieldErrors names the field, for example {"invoices": ["due_date must be a YYYY-MM-DD date or an ISO-8601 date-time with Z or an offset"]}.

Line items​

Garfield needs each invoice's line items (description, quantity and unit price) to take a claim forward. Send them in invoices[].line_items, or attach the invoice as a document and Garfield reads them from it. Each line item has:

  • description (required): what the business supplied.
  • quantity (required): a decimal string greater than zero, with at most 9 digits before the decimal point and 4 after it, for example "2" or "1.5".
  • unit_price (required): the price for each unit before tax, in the same format as total_amount_for_invoice.
  • tax (optional): the tax on that line, in the same format.

Eligibility​

The API accepts any well-formed claim. Garfield then checks whether it can take it forward. If it cannot, the claim shows status: "invalid" with reasons in GET /claims. Garfield handles:

  • Defendants in England and Wales. The defendant's postcode must be in England or Wales, and the contract must not give exclusive jurisdiction to courts elsewhere or require arbitration.
  • Small claims. The total, with interest and charges, must be within the £10,000 small claims track limit.
  • One claimant and one defendant for each claim.
  • Debts within the limitation period. Garfield cannot pursue an invoice that fell due more than six years ago (twelve for a contract made by deed), or that will reach that age within two weeks.
  • Money claims for goods and services, refunds, rent arrears and unpaid wages. Claims for damages, personal injury, fraud or regulated consumer credit are out of scope.

201 Created, or 200 OK on a replay:

{
"claim_id": "k4m9t",
"message_id": "a1b2c",
"document_ids": ["d3f4g"],
"already_existed": false
}

Every new claim gets a first message, so a create always returns a message_id. A replay returns the first call's message_id and document_ids. A replay of a claim created before Garfield began storing message and document ids returns message_id: null and document_ids: [].

A replay does not change the claim

A replay does not add documents or a message. A replay with a different body returns 409 idempotency_key_reused. To correct the claim or add documents later, add a message. See Errors and limits.

Step 3: list your claims​

curl "$BASE/claims?entity_id=aZ4kQ9pX2&limit=100" \
-H "Authorization: Bearer $TOKEN"
Query parameterRequiredNotes
entity_idNoThe entity. Leave it out to list your claims across every entity.
updated_sinceNoAn ISO-8601 date-time with Z or an offset. Only claims updated at or after it. Any other value fails with validation_failed (400).
limitNoPage size. Default 100, maximum 200. Below 1 means 1, above 200 means 200, and a value that is not a number means 100.
page_tokenNoThe next_page_token from the previous page, unchanged. Any other value fails with validation_failed (400).
partner_user_idNoOnly the claims with this partner_user_id. Requires entity_id.

Without updated_since, the newest claim is first. A test key lists its test claims, and a live key its live ones. Each claim has:

  • status: where the claim is. See Claim status. Base your logic on it.
  • needs_attention and reasons: see Claim status.
  • status_label: the stage as the business sees it. It is a label and can change.
  • stage: Garfield's internal stage. Garfield can rename or add internal stages without notice.
  • name: the name of the connected business, which is the claimant, or its entity_id if it has no name.
  • defendant_name, amount (the sum of the invoice totals, or null if Garfield cannot read any invoice total) and outstanding (what is still owed, with interest and charges). Both amounts are pounds as decimal strings, for example "4200.00". outstanding is null until Garfield has worked it out.
  • created_at and last_updated_at.
  • Your ids: partner_user_id, partner_claim_id and partner_invoice_ids.

Without entity_id, the list covers every entity, and each claim's entity_id says which one it belongs to. The list leaves out claims on an entity that disconnected from you or that you can no longer reach, so a page can hold fewer than limit claims. With the entity_id of an entity that disconnected from you, the API returns 403 partner_disconnected. entity_disconnected is deprecated and always false. next_page_token is null on the last page.

Claim status​

statusMeaning
receivedCreated. Garfield is checking it.
in_progressGarfield is preparing the claim before a letter goes out, or waiting for the business to choose an approach.
letter_sentA chaser or letter before action has gone out. Garfield is waiting for, or handling, a reply.
courtGarfield is preparing court proceedings, or they are under way, up to and including judgment.
enforcementGarfield is enforcing a judgment.
resolvedThe claim is closed or closing.
invalidGarfield cannot take the claim forward as it stands. reasons says why.
archivedThe business archived the claim.

These values are stable. Garfield never renames or removes them, and maps new internal stages onto them.

needs_attention is true when the claim will not move on by itself: it is invalid, or Garfield is waiting on the business for information, a decision or a document review.

reasons lists why an invalid claim is invalid, as { code, message }. Base your logic on code. message is one sentence for display. reasons is empty for every other status, and for claims that became invalid before Garfield began recording reasons.

codeWhy
wrong_jurisdiction_debtorThe defendant is outside England and Wales, or Garfield could not confirm where they are.
invalid_postcodeThe defendant's postcode is no longer in use.
amount_too_highThe claim is above the £10,000 small claims track limit.
invalid_amountGarfield cannot work out the claim's total from the invoices.
outside_limitation_periodAn invoice is outside, or too close to, the limitation period.
more_than_one_debtor, more_than_one_claimantGarfield handles one defendant and one claimant for each claim.
arbitration_clause, wrong_jurisdiction_clauseThe contract sends disputes to arbitration or to courts outside England and Wales.
damages_claim, fraud_claim, loan_dispute, personal_injury, flight_compensation_claim, not_debt_claimThe claim is not a money claim Garfield handles.
outside_small_claims_trackThe court has allocated, or intends to allocate, it to another track.
duplicate_claim, not_enforceableThe judgment the claim would enforce already belongs to another claim in Garfield, or has no enforcement route available.
pausedGarfield's team has paused the claim.
wrong_jurisdiction_claimantThe claimant is outside England and Wales, or Garfield could not confirm where they are.

Garfield can add new codes, so treat an unknown one as a general reason.

Poll for changes​

Send updated_since to get only the claims that changed. The least recently updated comes first. Garfield stamps last_updated_at when it starts working on a claim, which can be a few minutes before it saves the change. So start each poll from the largest last_updated_at you have seen, minus 15 minutes:

curl "$BASE/claims?updated_since=2026-10-01T09:00:00Z" \
-H "Authorization: Bearer $TOKEN"

Claims in that 15-minute overlap can come back more than once, so make processing the same claim twice harmless.

Read one claim​

GET /claims/{claim_id} returns one claim, in the same shape as a claim from GET /claims:

curl "$BASE/claims/k4m9t" \
-H "Authorization: Bearer $TOKEN"

Once the claim's entity disconnects from you, the API returns 403 partner_disconnected. 404 claim_not_found means you have not created a claim with that claim_id using a key of this mode (a test key cannot read a live claim, or the reverse), or the claim is on an entity you can no longer reach.

Step 4: add to a claim​

POST /claims/{claim_id}/messages adds a note, documents or both to a claim you created. Garfield reads it as it reads the first message and takes the claim forward. Use it to correct a detail, such as a new defendant postcode, to give Garfield what it is waiting for when needs_attention is true, or to send a document you did not have before.

curl -X POST "$BASE/claims/k4m9t/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"partner_message_id": "note-2026-00412-1",
"message": "Correction: the debtor moved to 14 Mill Lane, Sheffield S3 8AB. Signed delivery note attached.",
"document_refs": [
{
"storage_path": "entities/aZ4kQ9pX2/claims/pc_af66fa4b5cb7283356863b7554d4e140/documents/Q2xhaW0xYI.pdf",
"mime_type": "application/pdf",
"filename": "delivery-note-00412.pdf"
}
]
}'
  • message: free text, up to 10,000 characters.
  • document_refs: up to 20 files, each uploaded first with the claim's partner_claim_id, with the same checks as on POST /claims. A file already attached to the claim returns 400 document_path_invalid. Upload it again to send a new copy.
  • partner_message_id (optional): your id for this message. It makes a retry safe.

Send message, document_refs or both. A request with neither returns 400 validation_failed. You can add messages only while you can still create claims for the entity, so 403 partner_disconnected and entity_not_verified mean the same as on POST /claims. A test key adds to its test claims.

201 Created, or 200 OK on a replay of a partner_message_id:

{ "message_id": "m7p2q", "document_ids": ["d8h3k"], "already_existed": false }

The API stamps the claim's last_updated_at with the time of the call, so the next poll with updated_since picks the claim up.

Without partner_message_id, every call adds a message, so a retry after a timeout can add the same note twice. With it, a retry with the same body returns the first message_id and document_ids and adds nothing. The same partner_message_id with a different body returns 409 idempotency_key_reused. 409 claim_archived means the business archived the claim, so you cannot add anything more to it.

Node example​

This helper does steps 1 and 2. It uploads each file, then creates the claim.

import { readFile } from 'node:fs/promises'
import { basename } from 'node:path'

const BASE = 'https://www.garfield.law/api/partner/v1'
const TOKEN = process.env.GARFIELD_API_KEY! // 'pk_test_<partner_id>.<key_id>.<secret>'

const jsonHeaders = {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
}

// Get a signed URL, then PUT the file to storage.
// Returns the document_refs entry to pass to POST /claims.
async function uploadDocument(
entityId: string,
partnerClaimId: string,
filePath: string,
mimeType: string
) {
const filename = basename(filePath)

const mint = await fetch(`${BASE}/documents/upload-url`, {
method: 'POST',
headers: jsonHeaders,
body: JSON.stringify({
entity_id: entityId,
partner_claim_id: partnerClaimId,
filename,
mime_type: mimeType,
}),
})
if (!mint.ok)
throw new Error(`upload-url failed: ${(await mint.json()).code}`)
const { upload_url, storage_path, required_headers } = await mint.json()

// Send required_headers exactly as returned.
const put = await fetch(upload_url, {
method: 'PUT',
headers: required_headers,
body: await readFile(filePath),
})
if (!put.ok) throw new Error(`upload PUT failed: ${put.status}`)

return { storage_path, mime_type: mimeType, filename }
}

// Upload every file first (in parallel), then create the claim.
export async function createClaimWithDocuments(input: {
entityId: string
partnerClaimId: string
message?: string
defendant: { name: string } & Record<string, unknown>
invoices: Array<Record<string, unknown>>
files: Array<{ path: string; mimeType: string }>
}) {
const document_refs = await Promise.all(
input.files.map((f) =>
uploadDocument(input.entityId, input.partnerClaimId, f.path, f.mimeType)
)
)

const res = await fetch(`${BASE}/claims`, {
method: 'POST',
headers: jsonHeaders,
body: JSON.stringify({
entity_id: input.entityId,
partner_claim_id: input.partnerClaimId,
message: input.message ?? null,
defendant: input.defendant,
invoices: input.invoices,
document_refs,
}),
})
if (!res.ok)
throw new Error(`create claim failed: ${(await res.json()).code}`)

// { claim_id, message_id, document_ids, already_existed }
return res.json()
}