Skip to content

Authentication

Partifact reproduces the API’s authentication contract — the install handshake, the two headers, the org-scoped key, and the type-tagged error model — faithfully enough to build a real client against. This page shows exactly how it works, proves the gate is real (the rejections), and is upfront about exactly what’s simplified.

Every authenticated call carries two headers — the same shape the real API uses:

Authorization: Bearer <api_key>
Partly-Integration-ID: <integration_id>

The lone exception is the install call itself (integrations.insert), which is unauthenticated — it’s how you obtain those two values in the first place.

Provision credentials without a human (the install handshake)

Section titled “Provision credentials without a human (the install handshake)”

A real integration shouldn’t run on a hand-pasted key. The install call exchanges an OAuth grant for a fresh, long-lived credential:

  1. You hold an OAuth grantclient_id, client_secret, and a single-use access_code. (The sandbox seeds demo fixtures so you can run this immediately.)

  2. Exchange it at the one unauthenticated endpoint:

    Terminal window
    curl -s -X POST \
    https://partifact-mock-rails.thanhvuttv.workers.dev/api/2026-01/integrations.insert \
    -H "Content-Type: application/json" \
    -d '{"client_id":"partly_client_demo","client_secret":"secret_demo_8f3a","access_code":"ac_demo_valid_15m"}'
  3. You get back a fresh credential — a new api_key + integration_id every time:

    { "api_key": "partly_caa7bb17f08447dca1474ff8f74b2158",
    "integration_id": "e7019e6d-9b61-4932-b45b-e792547cdb5a" }
  4. Use those two values as the Authorization: Bearer and Partly-Integration-ID headers on every subsequent call.

You don’t have to run the install to start — the sandbox pre-loads ready-to-use repairer and supplier credentials wired to the seeded Corolla job, so you can make an authenticated call with zero setup. The exact values live on Credentials & tokens →.

Use the install handshake (above) to prove the contract end to end; use the pre-loaded creds to drive the seeded job immediately.

Proof the gate is real: the rejection model

Section titled “Proof the gate is real: the rejection model”

Auth that can’t reject anything isn’t auth. Each failure is a type-tagged body (the variant object is the response; the HTTP status is added for realism). All four are live right now:

Try thisResponseMeaning
Install with an unknown client_id404 {"type":"integration_not_found"}no such integration client
Install with a bad or already-used access_code401 {"type":"invalid_access_code"}the grant code is invalid/spent (single-use)
Any call with a missing or wrong bearer401 {"type":"unauthorized"}not authenticated
A repairer key calling a supplier-scope method403 {"type":"forbidden"}authenticated, but wrong role/scope
Terminal window
# wrong scope — a repairer credential on a supplier-only method:
curl -s -o /dev/null -w "%{http_code}\n" -X POST \
https://partifact-mock-rails.thanhvuttv.workers.dev/api/2026-01/supplier.procurements.confirm \
-H "Authorization: Bearer partly_demo_repairer_3f8a1c0d9e2b4a67b1c2" \
-H "Partly-Integration-ID: 0c000000-0000-4000-8000-000000000001" \
-H "Content-Type: application/json" -d '{"identity":{"id":"00000000-0000-4000-8000-000000000000"}}'
# -> 403 {"type":"forbidden"}

The distinction between unauthorized (no/bad credential), forbidden (right credential, wrong role), and not_found (right role, wrong org — a cross-org read is hidden, not denied) is the same privacy-preserving scoping the real contract uses.

Calibrated honesty — we say exactly what we built, and own the simplification:

Faithful to the contractDeliberately simplified (and why)
The install RPC (integrations.insert) exchanging a grant for a credentialNo external authorization server — the grant is seeded demo fixtures, so the demo needs nothing stood up
The two-header auth on every callNo PKCE / no refresh tokens / no real token-expiry clock
Org-scoped api_key; role/scope gating (forbidden)Credentials live in the sandbox’s store, not a real IdP
Single-use access codes (invalid_access_code on reuse)The fixture is named …_15m but the enforced behaviour is single-use, not a timed window
The type-tagged, per-method error model

We chose the self-contained stub to keep the artifact runnable and frictionless — and we’re aware of the limitation: this demonstrates the authentication contract and its failure modes, not a production security implementation. That boundary is stated, not hidden.