IAMScouting Public REST API (v1)
The /api/v1/* surface lets third-party services and clubs read their own
data and post programmatically from servers, scripts, and CRMs. It is the
public, machine-readable counterpart of the in-app /api/network/* routes
(which are session-cookie authenticated and intended for the browser).
> Status: v1 stable. Breaking changes will introduce a /api/v2/* rather
> than mutate v1 in place.
---
Authentication
Every /api/v1/* request must carry a bearer token in the Authorization
header:
Authorization: Bearer iams_live_<your_32_hex_key>
Generate keys at /account/api-keys. Each key:
- belongs to a single user account (the key acts as that user)
- carries one or more scopes (
read,write,admin) - is shown exactly once at creation — store it in a password manager or
a secrets store, never in client-side code or a public repo
- can be revoked at any time from the same page; revocation takes effect
immediately
There is no refresh / rotation endpoint — to rotate, create a new key, swap
it in, then revoke the old one.
Scopes
| Scope | What it grants |
|-------|----------------|
| read | GET endpoints (list / read your own data). Required minimum. |
| write | POST / PATCH / DELETE on your own data. |
| admin | Implicit superset of read + write. Use sparingly. |
A request to a route that needs write with a key that only has read
returns 403 insufficient_scope — the response includes required_scope
and your_scopes so the integration can surface a clear error.
---
Rate limits
Every request runs through a single shared bucket per key:
| Limit | Window | Bucket |
|-------|--------|--------|
| 1000 requests | per hour (rolling 3600 s) | api_v1, keyed on the API key id |
When a key exceeds the limit, the response is:
HTTP/1.1 429 Too Many Requests
Retry-After: 1832
Content-Type: application/json
{ "error": "rate_limited", "retry_after_seconds": 1832, "limit": 1000, "bucket": "api_v1" }
Honour Retry-After; back off until the next window. Need a higher ceiling?
Email start@iamscouting.com and we'll discuss a partner SLA.
---
Error codes
All errors are JSON with an error (machine code) and usually a detail
(human message).
| HTTP | error | When |
|------|---------|------|
| 400 | invalid_json | Body wasn't valid JSON. |
| 400 | invalid_<field> | A specific field failed validation (see detail). |
| 401 | unauthorized | Missing / malformed / unknown / revoked bearer token. |
| 403 | insufficient_scope | Key lacks the required scope. |
| 403 | agent_only / agent_not_verified | Lead endpoints require a verified agent persona. |
| 403 | upgrade_required | Posting requires a paid subscription tier. |
| 403 | *_limit_reached | You're at your tier's active-object cap. |
| 404 | not_found | The id doesn't exist. |
| 404 | user_not_found | /api/v1/me couldn't resolve the user (unusual). |
| 429 | rate_limited | See above. |
| 429 | pin_limit_reached | Active-pin cap reached (scout tier dependent). |
| 500 | server error | Something failed inside the API. |
---
Endpoints
All examples assume:
export IAMS_KEY="iams_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export IAMS_BASE="https://iamscouting.com/api/v1"
GET /api/v1/me
Return the authenticated user's profile.
curl -s "$IAMS_BASE/me" \
-H "Authorization: Bearer $IAMS_KEY"
Response
{
"id": "uuid",
"email": "you@club.com",
"name": "Your Name",
"avatar_url": null,
"persona": "agent",
"display_handle": "agent-xyz",
"country": "DE",
"agent_verified": true,
"trust_score": 72,
"scopes": ["read", "write"]
}
Required scope: read.
---
GET /api/v1/leads
List your own leads (any status). Optional ?status=published|paused|closed.
Optional ?limit= (1–200, default 50).
curl -s "$IAMS_BASE/leads" -H "Authorization: Bearer $IAMS_KEY"
Returns { "leads": [ … ] }. Required scope: read.
POST /api/v1/leads
Create a new lead. Body schema mirrors the in-app endpoint
(/api/network/leads POST):
curl -s "$IAMS_BASE/leads" \
-H "Authorization: Bearer $IAMS_KEY" \
-H "Content-Type: application/json" \
-d '{
"player_name": "Alex Example",
"position": "AM/RW",
"age": 21,
"current_club": "FC Example",
"ask_price_min": 1500000,
"ask_price_max": 2500000,
"exclusivity": "exclusive",
"visibility": "pro",
"expires_in_days": 21,
"notes": "Available January window."
}'
Returns { "ok": true, "lead": { … } }. Required scope: write.
Caller must be a verified agent (persona = agent + agent_verified_at set)
on at least Starter tier. Tier caps + roster-match rules apply identically
to the in-app route.
---
GET /api/v1/jobs
List your own job listings. Same shape + params as /api/v1/leads.
POST /api/v1/jobs
Create a job listing. Body mirrors /api/network/jobs POST: club_name,
role_title required; role_type, location, country, salary_min,
salary_max, description, requirements, apply_deadline, visibility,
expires_in_days optional.
Required scope: write. Starter tier minimum.
---
GET /api/v1/pins
List your scouting-trip pins. Optional ?limit= (1–500, default 100).
POST /api/v1/pins
Create a pin.
curl -s "$IAMS_BASE/pins" \
-H "Authorization: Bearer $IAMS_KEY" \
-H "Content-Type: application/json" \
-d '{
"dest_city": "Porto",
"dest_country": "Portugal",
"start_date": "2026-08-12",
"end_date": "2026-08-14",
"identity_tier": "T1",
"note": "Watching FC Porto B vs. Estoril."
}'
Required scope: write. Subject to your scout-tier active-pin cap.
PATCH /api/v1/pins/{id}
Edit one of your own pins. Body accepts any subset of pin fields. Returns
{ "ok": true }. Required scope: write.
DELETE /api/v1/pins/{id}
Delete one of your own pins. Required scope: write.
---
GET /api/v1/watchlist
List your match watchlist rows.
POST /api/v1/watchlist
Add a fixture.
# Smrt-source fixture
curl -s "$IAMS_BASE/watchlist" \
-H "Authorization: Bearer $IAMS_KEY" \
-H "Content-Type: application/json" \
-d '{ "source": "smrt", "match_id": 12345, "priority": 2 }'
# External-source fixture
curl -s "$IAMS_BASE/watchlist" \
-H "Authorization: Bearer $IAMS_KEY" \
-H "Content-Type: application/json" \
-d '{ "source": "football_data", "external_id": "match_id_from_provider" }'
Valid source: smrt, football_data, wikidata, ugc, openligadb,
openfootball. Smrt-source rows require a numeric match_id; all other
sources require external_id.
Required scope: write.
DELETE /api/v1/watchlist?id={watchlist_row_uuid}
Remove a row. Required scope: write.
---
Key management (not part of /api/v1)
Key CRUD is session-cookie authenticated, not API-key authenticated. You
manage keys from a browser while signed in, then hand the keys to your
integration:
GET /api/keys— list your keys (never returns the plaintext).POST /api/keys— generate a key. Body:{ label, scopes? }. Response
includes a one-time full_key field — store it now or lose it.
DELETE /api/keys/{id}— revoke a key (soft-delete; setsrevoked_at).
These three routes also live behind the normal app middleware (auth, CSRF
posture identical to other /api/* browser routes) and are NOT subject to
the /api/v1 rate-limit bucket.
---
Webhooks
Not yet. v1 is pull-only — your integration polls the relevant GET
endpoint, or subscribes to in-app alerts at /network/alerts. Outbound
webhooks are on the roadmap (track in ROADMAP.md).
---
Versioning
v1is stable. Field additions are non-breaking and won't bump the version.- Removing or renaming a field, changing a response shape, or tightening a
validation rule constitutes a break and will ship as v2 with v1
remaining live in parallel for at least 6 months.
- Breaking-change notifications go to the email on file for each user that
has ever created an API key.