Quickstart
From API key to evaluation result in six calls — pick a template, create a submission, upload a file, register a webhook, and start the evaluation.
This walks the full happy path: authenticate, pick a template, create a submission, upload a document, register a webhook, and start the evaluation that pushes the result back to you. Every step shows curl and the three SDKs — pick your language with the tabs.
Note
The base URL https://api.aircredit.de/partner is the real one, but it is not publicly reachable
yet. Requests in this guide will not reach a live server; they exist to show the shape of each
call.
0. Get an API key
Your API key is issued per API client from the AirCredit console. Treat it like a password: it grants full access to your client's submissions. Keep it server-side and pass it as a bearer token on every request. See Authentication for handling and rotation.
Set it once in your environment:
export AIRCREDIT_API_KEY="sk_live_your_key_here"
1. Pick a template
A template defines what a submission collects and verifies: its document slots, checks, and
data fields. list_templates returns the builtin templates (stable string keys) and any custom
templates you have authored — the default builtin is a fine starting point. See
Templates & customization for authoring your own.
curl https://api.aircredit.de/partner/v1/templates \
-H "Authorization: Bearer $AIRCREDIT_API_KEY"const templates = await client.listTemplates();
const template = templates.data.find(t => t.isDefault) ?? templates.data[0];
console.log(template.id, template.displayName); // -> "default_personal_real_estate_financing", …templates = client.list_templates()
template = next((t for t in templates.data if t.is_default), templates.data[0])
print(template.id, template.display_name) # -> "default_personal_real_estate_financing", …let templates = client.list_templates().send().await?.into_inner();
let template = templates.data.iter()
.find(|template| template.is_default)
.unwrap_or(&templates.data[0]);
println!("{} {}", template.id, template.display_name);2. Create a submission
A submission is created from the template and keyed by your own external_submission_id. That
external id is the idempotency key — reuse it and you get the same submission back instead of a
duplicate.
curl -X POST https://api.aircredit.de/partner/v1/submissions \
-H "Authorization: Bearer $AIRCREDIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "6f5f0f0a-1a11-4a7e-9c9e-9a1b0c3d2e4f",
"external_submission_id": "loan-2026-000451",
"metadata": { "branch": "muc-01" },
"language": "de"
}'import { createClient } from "@aircredit/partner-sdk";
const client = createClient({ apiKey: process.env.AIRCREDIT_API_KEY! });
const submission = await client.createSubmission({
templateId: "6f5f0f0a-1a11-4a7e-9c9e-9a1b0c3d2e4f",
externalSubmissionId: "loan-2026-000451",
metadata: { branch: "muc-01" },
language: "de",
});
console.log(submission.id, submission.status); // -> "…", "collecting_documents"import os
from aircredit_partner import Client
client = Client(api_key=os.environ["AIRCREDIT_API_KEY"])
submission = client.create_submission(
template_id="6f5f0f0a-1a11-4a7e-9c9e-9a1b0c3d2e4f",
external_submission_id="loan-2026-000451",
metadata={"branch": "muc-01"},
language="de",
)
print(submission.id, submission.status) # -> "…", "collecting_documents"use aircredit_partner::Client;
let client = Client::new_with_api_key(
"https://api.aircredit.de/partner",
std::env::var("AIRCREDIT_API_KEY")?,
);
let submission = client
.create_submission()
.body_map(|b| b
.template_id("6f5f0f0a-1a11-4a7e-9c9e-9a1b0c3d2e4f")
.external_submission_id("loan-2026-000451")
.language(Some("de".to_string())))
.send()
.await?
.into_inner();
println!("{} {:?}", submission.id, submission.status);A 201 Created means authorization has fully propagated and the submission is immediately usable.
If propagation is still catching up you may get 503 authz_not_applied_yet — just retry with the
same external_submission_id. Keep the returned id; you need it for everything below.
3. Upload a document
Send the file as multipart/form-data with an external_file_id text part and a file part. The
external_file_id is this upload's idempotency key. Accepted types are PDF, JPEG, PNG, and Word;
the default size limit is 100 MiB. See Uploads for the full rules.
curl -X POST \
https://api.aircredit.de/partner/v1/submissions/$SUBMISSION_ID/files \
-H "Authorization: Bearer $AIRCREDIT_API_KEY" \
-F "external_file_id=payslip-jan" \
-F "file=@./payslip-january.pdf;type=application/pdf"import { readFile } from "node:fs/promises";
const upload = await client.createUpload({
submissionId: submission.id,
externalFileId: "payslip-jan",
file: new File([await readFile("./payslip-january.pdf")], "payslip-january.pdf", {
type: "application/pdf",
}),
});
console.log(upload.uploadStatus); // -> "processing"with open("payslip-january.pdf", "rb") as f:
upload = client.create_upload(
submission_id=submission.id,
external_file_id="payslip-jan",
file=("payslip-january.pdf", f, "application/pdf"),
)
print(upload.upload_status) # -> "processing"let bytes = tokio::fs::read("payslip-january.pdf").await?;
let upload = client
.create_upload()
.submission_id(&submission.id)
.external_file_id("payslip-jan")
.file(bytes, "payslip-january.pdf", "application/pdf")
.send()
.await?
.into_inner();
println!("{:?}", upload.upload_status); // -> ProcessingYou get 202 Accepted with the upload in status processing. Processing is asynchronous — don't
block on the response. Until webhooks are wired up (next step), you can poll the submission and
watch each upload's upload_status move to ready:
curl https://api.aircredit.de/partner/v1/submissions/$SUBMISSION_ID \
-H "Authorization: Bearer $AIRCREDIT_API_KEY"const detail = await client.getSubmission({ submissionId: submission.id });
for (const slot of detail.documentSlots) {
for (const u of slot.uploads) {
console.log(slot.key, u.externalFileId, u.uploadStatus);
}
}detail = client.get_submission(submission_id=submission.id)
for slot in detail.document_slots:
for u in slot.uploads:
print(slot.key, u.external_file_id, u.upload_status)let detail = client
.get_submission()
.submission_id(&submission.id)
.send()
.await?
.into_inner();
for slot in &detail.document_slots {
for upload in &slot.uploads {
println!("{} {} {:?}", slot.key, upload.external_file_id, upload.upload_status);
}
}Tip
Polling is fine to get started, but webhooks are the intended path for production. Poll no more than a few times per minute and always prefer the webhook signal.
4. Register a webhook endpoint
Register an HTTPS endpoint once and AirCredit pushes every event to it — starting with the
file.ready for the upload above. The response is the only time the endpoint's signing_secret
is ever returned — store it immediately; you need it to verify deliveries.
curl -X POST https://api.aircredit.de/partner/v1/webhook_endpoints \
-H "Authorization: Bearer $AIRCREDIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example-partner.com/aircredit/webhooks",
"description": "prod receiver"
}'const endpoint = await client.createWebhookEndpoint({
url: "https://example-partner.com/aircredit/webhooks",
description: "prod receiver",
});
// Store endpoint.signingSecret now — it is never returned again.
await saveSecret(endpoint.id, endpoint.signingSecret);endpoint = client.create_webhook_endpoint(
url="https://example-partner.com/aircredit/webhooks",
description="prod receiver",
)
# Store endpoint.signing_secret now — it is never returned again.
save_secret(endpoint.id, endpoint.signing_secret)let endpoint = client
.create_webhook_endpoint()
.body_map(|b| b
.url("https://example-partner.com/aircredit/webhooks")
.description(Some("prod receiver".to_string())))
.send()
.await?
.into_inner();
// Store endpoint.signing_secret now — it is never returned again.
save_secret(&endpoint.id, endpoint.signing_secret.as_deref())?;5. Start the evaluation
Once the documents you care about are ready, start the evaluation. It runs asynchronously —
agentically working through the template's checks and data fields to a summary verdict — see
Evaluations for the full lifecycle.
curl -X POST https://api.aircredit.de/partner/v1/submissions/$SUBMISSION_ID/evaluations \
-H "Authorization: Bearer $AIRCREDIT_API_KEY"const evaluation = await client.startEvaluation({ submissionId: submission.id });
console.log(evaluation.id, evaluation.status); // -> "…", "queued"evaluation = client.start_evaluation(submission_id=submission.id)
print(evaluation.id, evaluation.status) # -> "…", "queued"let evaluation = client
.start_evaluation()
.submission_id(&submission.id)
.send()
.await?
.into_inner();
println!("{} {:?}", evaluation.id, evaluation.status);6. Receive the result
Your endpoint now receives the story as it happens: file.ready when an upload finishes,
evaluation.started, possibly a few evaluation.progress, and finally evaluation.completed
carrying the outcome, recommendation, and risk band. Each delivery is the canonical event envelope;
the X-Airtype-Signature header lets you verify it came from AirCredit. Respond with any 2xx
within 30 seconds to acknowledge.
{
"id": "evt_2f0d9c62-6f6e-4a7e-8f8e-2d5c7a1b0c3d",
"type": "file.ready",
"created_at": "2026-02-01T10:32:04Z",
"api_client_id": "…",
"data": {
"upload": {
"id": "…",
"external_file_id": "payslip-jan",
"external_submission_id": "loan-2026-000451",
"upload_status": "ready",
"file_id": "…"
}
}
}
Verifying the signature is a few lines in every language — see the Webhooks guide for the full verification snippet per SDK. For the full typed result — per-check verdicts, extracted fields, confidence, citations — fetch the evaluation itself: Reading the result.
Where to go next
- Templates & customization — author templates, checks, and data fields.
- Uploads — content types, idempotency, slot assignment, and re-uploads.
- Evaluations — lifecycle, the typed result, confidence and citations.
- Webhooks — the signature scheme, retries, rotation, and deduplication.
- Errors — the error envelope and every code you might see.
- API reference — try each call live against the console.